User management completed. Leptos upgraded to 0.5.0.
This commit is contained in:
Generated
+71
-160
@@ -382,9 +382,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "attribute-derive"
|
||||
version = "0.6.1"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c124f12ade4e670107b132722d0ad1a5c9790bcbc1b265336369ea05626b4498"
|
||||
checksum = "0c94f43ede6f25dab1dea046bff84d85dea61bd49aba7a9011ad66c0d449077b"
|
||||
dependencies = [
|
||||
"attribute-derive-macro",
|
||||
"proc-macro2",
|
||||
@@ -394,13 +394,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "attribute-derive-macro"
|
||||
version = "0.6.1"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b217a07446e0fb086f83401a98297e2d81492122f5874db5391bd270a185f88"
|
||||
checksum = "b409e2b2d2dc206d2c0ad3575a93f001ae21a1593e2d0c69b69c308e63f3b422"
|
||||
dependencies = [
|
||||
"collection_literals",
|
||||
"interpolator",
|
||||
"proc-macro-error",
|
||||
"manyhow",
|
||||
"proc-macro-utils",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -414,12 +414,6 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.20.0"
|
||||
@@ -742,16 +736,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"json5",
|
||||
"lazy_static",
|
||||
"nom",
|
||||
"pathdiff",
|
||||
"ron",
|
||||
"rust-ini",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1002,12 +991,6 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
@@ -1603,6 +1586,15 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.6"
|
||||
@@ -1627,17 +1619,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json5"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "language-tags"
|
||||
version = "0.3.2"
|
||||
@@ -1655,9 +1636,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos"
|
||||
version = "0.5.0-rc1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d811de15430df8e4886afe09e5e741a886876c51ea32b8f11e0963ba9415e4b"
|
||||
checksum = "4d3885e75a25bbf43c95350cf2f6b9f5228a3d911e28512c44c2a6c8aa49e9c9"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"leptos_config",
|
||||
@@ -1675,9 +1656,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_actix"
|
||||
version = "0.5.0-rc1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82ae271646ca49e2464ee454428775064a330658559e1c92d20170ee9c12b674"
|
||||
checksum = "bd04ab9afac818fe45695b8e1f103714e612135b42519a21761c730fc9223c14"
|
||||
dependencies = [
|
||||
"actix-http",
|
||||
"actix-web",
|
||||
@@ -1695,9 +1676,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_config"
|
||||
version = "0.5.0-rc1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92015175317cafe8e651289a48bc3cdd68c276c6054229f20dbee73a81fea21f"
|
||||
checksum = "d3936a83035a4ec03487792d8c9c2c5ad00c269d09701d102630ac5c31caa463"
|
||||
dependencies = [
|
||||
"config",
|
||||
"regex",
|
||||
@@ -1708,9 +1689,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_dom"
|
||||
version = "0.5.0-rc1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba03b4357fd08d2c84da3572f66f47973703e0ef6166c2dd0db4766f83310a72"
|
||||
checksum = "6cbea8aeea07633b3559818fa963c03857751fbafc6bb4a73c995662836070e1"
|
||||
dependencies = [
|
||||
"async-recursion",
|
||||
"cfg-if",
|
||||
@@ -1719,7 +1700,7 @@ dependencies = [
|
||||
"getrandom",
|
||||
"html-escape",
|
||||
"indexmap 2.0.0",
|
||||
"itertools",
|
||||
"itertools 0.10.5",
|
||||
"js-sys",
|
||||
"leptos_reactive",
|
||||
"once_cell",
|
||||
@@ -1738,9 +1719,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_hot_reload"
|
||||
version = "0.5.0-rc1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21cade49ea1a5e72b3a546b4507b3ef4e43586d306b7f20c180ed6a3bf855665"
|
||||
checksum = "b56ec18e255737108b4f4d570c1c4f036f54a9989befe2658758500b636ebda4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
@@ -1756,9 +1737,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_integration_utils"
|
||||
version = "0.5.0-rc1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb2d6a0d32b073fa1a9326f83b99f02f60a0e159fe0a7ce53d2708318114a6a3"
|
||||
checksum = "77e1faf41644272929c47993af12928a51c0a03a1ed7ee55afcacf4a3a02073c"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"leptos",
|
||||
@@ -1770,15 +1751,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_macro"
|
||||
version = "0.5.0-rc1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "befb8d664269550a918a6a160c10792951a37e95b542ca454c191ec7480505de"
|
||||
checksum = "dae8be584ba63e002cec113e0a831f2ba17ad452104781a2b1b65555db049779"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"cfg-if",
|
||||
"convert_case 0.6.0",
|
||||
"html-escape",
|
||||
"itertools",
|
||||
"itertools 0.11.0",
|
||||
"leptos_hot_reload",
|
||||
"prettyplease",
|
||||
"proc-macro-error",
|
||||
@@ -1793,9 +1774,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_meta"
|
||||
version = "0.5.0-rc1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c77f3ae209084fae41024f3023fa816a6fe91a45da00729ff349dd0ddb9b2c0"
|
||||
checksum = "013e23a79d48c6eee6063b162e5ba0beb7d1a42c07361e4c16effb916160a5f0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"indexmap 2.0.0",
|
||||
@@ -1807,15 +1788,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_reactive"
|
||||
version = "0.5.0-rc1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4382426f6e79d209408e362883b9542f934bafb3b98cc957f94e085f707508a0"
|
||||
checksum = "90ec5366c79892fa8232dcfa6f05d610d0fd780af155fea8c466e77da18e744f"
|
||||
dependencies = [
|
||||
"base64 0.21.2",
|
||||
"cfg-if",
|
||||
"futures",
|
||||
"indexmap 2.0.0",
|
||||
"js-sys",
|
||||
"pin-project",
|
||||
"rustc-hash",
|
||||
"self_cell",
|
||||
"serde",
|
||||
@@ -1832,9 +1814,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_router"
|
||||
version = "0.5.0-rc1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b250d706ddbfb9991c5615b35dd27596e5b23024d8ee50fe4717aa21f67bb3b1"
|
||||
checksum = "f55951a1e4ee0b9c26e4ebc0c09ecc4b7fffbefecb912c72db0c5dfa33b1584c"
|
||||
dependencies = [
|
||||
"cached",
|
||||
"cfg-if",
|
||||
@@ -1843,6 +1825,8 @@ dependencies = [
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"leptos",
|
||||
"leptos_integration_utils",
|
||||
"leptos_meta",
|
||||
"linear-map",
|
||||
"lru",
|
||||
"once_cell",
|
||||
@@ -1861,9 +1845,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "leptos_server"
|
||||
version = "0.5.0-rc1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dfe03995c38441e45dc0d44828b657dbcff3b87c8ce5c2f4219297226c752b0"
|
||||
checksum = "520f4f7221a323c029877ffb09e97d38cc805f1a5821f9554ecf0e7f6852100c"
|
||||
dependencies = [
|
||||
"inventory",
|
||||
"lazy_static",
|
||||
@@ -1908,12 +1892,6 @@ dependencies = [
|
||||
"serde_test",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.5"
|
||||
@@ -1963,6 +1941,29 @@ dependencies = [
|
||||
"hashbrown 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "manyhow"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "516b76546495d933baa165075b95c0a15e8f7ef75e53f56b19b7144d80fd52bd"
|
||||
dependencies = [
|
||||
"manyhow-macros",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "manyhow-macros"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ba072c0eadade3160232e70893311f1f8903974488096e2eb8e48caba2f0cf1"
|
||||
dependencies = [
|
||||
"proc-macro-utils",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.9.1"
|
||||
@@ -2123,16 +2124,6 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-multimap"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a"
|
||||
dependencies = [
|
||||
"dlv-list",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pad-adapter"
|
||||
version = "0.1.1"
|
||||
@@ -2189,50 +2180,6 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f73935e4d55e2abf7f130186537b19e7a4abc886a0252380b59248af473a3fc9"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aef623c9bbfa0eedf5a0efba11a5ee83209c326653ca31ff019bec3a95bfff2b"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3e8cba4ec22bada7fc55ffe51e2deb6a0e0db2d0b7ab0b103acc80d2510c190"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a01f71cb40bd8bb94232df14b946909e14660e33fc05db3e50ae2a82d7ea0ca0"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pest",
|
||||
"sha2 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.0"
|
||||
@@ -2616,17 +2563,6 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ron"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"bitflags 1.3.2",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.2"
|
||||
@@ -2663,16 +2599,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ordered-multimap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust_decimal"
|
||||
version = "1.31.0"
|
||||
@@ -2883,9 +2809,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server_fn"
|
||||
version = "0.5.0-rc1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c5839ea169c9ac14c08d368ed523d0896b050cf18d754fcbde19923995f19be"
|
||||
checksum = "29eefae61211e81059a092a3428612c475a3a28e0ea4fb3fd49b0a940d837f84"
|
||||
dependencies = [
|
||||
"ciborium",
|
||||
"const_format",
|
||||
@@ -2908,9 +2834,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server_fn_macro"
|
||||
version = "0.5.0-rc1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80116501286c018b8d6330be8b2bc286ed801f59439f2fbfad2b79d96942c2a2"
|
||||
checksum = "f68140099f8e55bd526dc176d17d341189bf669d45216c4797ddc344610a84a4"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"proc-macro-error",
|
||||
@@ -2923,9 +2849,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server_fn_macro_default"
|
||||
version = "0.5.0-rc1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "709cc458808a38f20b9d0121a3022fa35b704e5e577d352b92b5dd4ad78f4923"
|
||||
checksum = "eee874f357d640ad221ba0c27c2559fa3d1434f7f7bbf688a34118518c5924b7"
|
||||
dependencies = [
|
||||
"server_fn_macro",
|
||||
"syn 2.0.28",
|
||||
@@ -3080,7 +3006,7 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"itertools 0.10.5",
|
||||
"nom",
|
||||
"unicode_categories",
|
||||
]
|
||||
@@ -3561,12 +3487,6 @@ version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.6.0"
|
||||
@@ -3963,15 +3883,6 @@ version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.0-rc.1"
|
||||
|
||||
+4
-4
@@ -12,10 +12,10 @@ actix-web = { version = "4.4.0", optional = true, features = ["macros"] }
|
||||
actix-session = { version = "0.8.0", optional = true, features = ["cookie-session"] }
|
||||
console_error_panic_hook = "0.1"
|
||||
cfg-if = "1"
|
||||
leptos = { version = "0.5.0-rc1" }
|
||||
leptos_meta = { version = "0.5.0-rc1" }
|
||||
leptos_actix = { version = "0.5.0-rc1", optional = true }
|
||||
leptos_router = { version = "0.5.0-rc1" }
|
||||
leptos = { version = "0.5.0" }
|
||||
leptos_meta = { version = "0.5.0" }
|
||||
leptos_actix = { version = "0.5.0", optional = true }
|
||||
leptos_router = { version = "0.5.0" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
wasm-bindgen = "=0.2.87"
|
||||
web-sys = { version = "0.3.61", features = ["Navigator"] }
|
||||
|
||||
+28
@@ -6,14 +6,42 @@ use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
use crate::components::admin_portal::AdminPortal;
|
||||
use crate::components::header::Header;
|
||||
use crate::components::user_menu::MenuOpener;
|
||||
use crate::pages::login::Login;
|
||||
use crate::pages::public::Public;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct MenuHelper {
|
||||
opened: RwSignal<MenuOpener>
|
||||
}
|
||||
|
||||
impl MenuHelper {
|
||||
pub fn new() -> Self {
|
||||
let opened = create_rw_signal(MenuOpener::new());
|
||||
Self {
|
||||
opened
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_opened(&self, opened: MenuOpener) {
|
||||
self.opened.set(opened);
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
self.opened.get().close();
|
||||
}
|
||||
|
||||
pub fn reset(&self) {
|
||||
self.opened.set(MenuOpener::new());
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
// Provides context that manages stylesheets, titles, meta tags, etc.
|
||||
provide_meta_context();
|
||||
init_locales();
|
||||
provide_context(MenuHelper::new());
|
||||
|
||||
view! {
|
||||
<Header/>
|
||||
|
||||
+13
-1
@@ -1,5 +1,6 @@
|
||||
//use chrono::{NaiveDate, NaiveTime, Weekday};
|
||||
//use rust_decimal::Decimal;
|
||||
#![allow(unused_variables)]
|
||||
use serde::{Deserialize, Serialize};
|
||||
//use uuid::Uuid;
|
||||
use validator::Validate;
|
||||
@@ -51,11 +52,16 @@ impl User {
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)]
|
||||
pub struct UserProfile {
|
||||
#[validate(length(min = 1,message = "Username cannot be empty"))]
|
||||
login: String,
|
||||
#[validate(must_match(other = "password_ver", message = "Passwords doesn't match"))]
|
||||
password: Option<String>,
|
||||
password_ver: Option<String>,
|
||||
full_name: String,
|
||||
#[validate(email(message = "Enter valid email address"))]
|
||||
email: String,
|
||||
get_emails: Option<String>
|
||||
get_emails: Option<String>,
|
||||
admin: Option<String>
|
||||
}
|
||||
|
||||
impl UserProfile {
|
||||
@@ -75,6 +81,12 @@ impl UserProfile {
|
||||
pub fn get_emails(&self) -> bool {
|
||||
self.get_emails.is_some()
|
||||
}
|
||||
pub fn admin(&self) -> bool {
|
||||
self.admin.is_some()
|
||||
}
|
||||
pub fn password(&self) -> &Option<String> {
|
||||
&self.password
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)]
|
||||
|
||||
+9
-1
@@ -28,8 +28,9 @@ macro_rules! user_check {
|
||||
use crate::backend::user::logged_in_user;
|
||||
|
||||
perm_check!(is_logged_in);
|
||||
let user = logged_in_user().await.unwrap_or(User::default());
|
||||
|
||||
if logged_in_user().await.unwrap_or(User::default()).login != $check {
|
||||
if !user.admin && user.login != $check {
|
||||
let response = expect_context::<ResponseOptions>();
|
||||
response.set_status(StatusCode::FORBIDDEN);
|
||||
|
||||
@@ -41,6 +42,9 @@ macro_rules! user_check {
|
||||
cfg_if!{
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use sqlx::PgPool;
|
||||
use actix_web::web::Data;
|
||||
use leptos_actix::extract;
|
||||
use leptos::ServerFnError;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppData {
|
||||
@@ -58,5 +62,9 @@ cfg_if!{
|
||||
&self.db_pool
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_pool() -> Result<PgPool, ServerFnError> {
|
||||
extract(|data: Data<AppData>| async move { data.db_pool().clone() }).await
|
||||
}
|
||||
}
|
||||
}
|
||||
+92
-23
@@ -60,15 +60,13 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
|
||||
#[server(Login, "/api")]
|
||||
pub async fn login(username: String, password: String) -> Result<ApiResponse<()>, ServerFnError> {
|
||||
use crate::backend::AppData;
|
||||
use actix_session::*;
|
||||
use actix_web::web::Data;
|
||||
use leptos_actix::extract;
|
||||
use actix_web::http::StatusCode;
|
||||
use leptos_actix::ResponseOptions;
|
||||
use crate::backend::get_pool;
|
||||
|
||||
let pool = extract(|data: Data<AppData>| async move { data.db_pool().clone() }).await?;
|
||||
|
||||
let pool = get_pool().await?;
|
||||
let user = user_from_login(&pool, &username).await.unwrap_or(User::default());
|
||||
|
||||
if !user.login.is_empty() && pwhash::bcrypt::verify(password, &user.password) {
|
||||
@@ -114,41 +112,48 @@ pub async fn get_user() -> Result<Option<User>, ServerFnError> {
|
||||
|
||||
#[server(GetUsers, "/api", "Url", "get_users")]
|
||||
pub async fn get_users() -> Result<ApiResponse<Vec<User>>, ServerFnError> {
|
||||
use crate::backend::AppData;
|
||||
use actix_web::web::Data;
|
||||
use leptos_actix::extract;
|
||||
use crate::perm_check;
|
||||
use crate::backend::get_pool;
|
||||
|
||||
perm_check!(is_admin);
|
||||
|
||||
let pool = extract(|data: Data<AppData>| async move { data.db_pool().clone() }).await?;
|
||||
let users = sqlx::query_as::<_, User>(r#"SELECT * FROM "user""#).fetch_all(&pool).await?;
|
||||
let pool = get_pool().await?;
|
||||
let users = sqlx::query_as::<_, User>(r#"SELECT * FROM "user" ORDER BY login"#).fetch_all(&pool).await?;
|
||||
|
||||
Ok(ApiResponse::Data(users))
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn update_profile(user: UserProfile) -> Result<ApiResponse<()>, ServerFnError> {
|
||||
use crate::backend::AppData;
|
||||
use actix_web::web::Data;
|
||||
use leptos_actix::extract;
|
||||
use crate::user_check;
|
||||
use crate::backend::get_pool;
|
||||
|
||||
user_check!(user.login());
|
||||
let usr = logged_in_user().await.unwrap_or(User::default());
|
||||
|
||||
let pool = extract(|data: Data<AppData>| async move { data.db_pool().clone() }).await?;
|
||||
sqlx::query(r#"UPDATE "user" SET full_name = $1, email = $2, get_emails = $3 WHERE login = $4"#)
|
||||
if !usr.admin && user.admin() {
|
||||
let response = expect_context::<ResponseOptions>();
|
||||
response.set_status(StatusCode::FORBIDDEN);
|
||||
|
||||
return Ok(ApiResponse::Error("You can't escalate your privileges".to_string()))
|
||||
}
|
||||
|
||||
let pool = get_pool().await?;
|
||||
sqlx::query(r#"UPDATE "user" SET full_name = $1, email = $2, get_emails = $3, admin = $4 WHERE login = $5"#)
|
||||
.bind(user.full_name())
|
||||
.bind(user.email())
|
||||
.bind(user.get_emails())
|
||||
.bind(user.admin())
|
||||
.bind(user.login())
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
let usr = user_from_login(&pool, user.login()).await?;
|
||||
extract(|session: Session| async move {
|
||||
let _ = session.insert("user", usr);
|
||||
}).await?;
|
||||
if logged_in_user().await.unwrap_or_default().login == user.login() {
|
||||
let usr = user_from_login(&pool, user.login()).await?;
|
||||
extract(|session: Session| async move {
|
||||
let _ = session.insert("user", usr);
|
||||
}).await?;
|
||||
}
|
||||
|
||||
Ok(ApiResponse::Data(()))
|
||||
}
|
||||
@@ -161,17 +166,17 @@ impl ForValidation for UpdateProfile {
|
||||
|
||||
#[server]
|
||||
pub async fn change_pwd(new_pw: PwdChange) -> Result<ApiResponse<()>, ServerFnError> {
|
||||
use crate::backend::AppData;
|
||||
use actix_web::web::Data;
|
||||
use leptos_actix::extract;
|
||||
use crate::user_check;
|
||||
use crate::backend::get_pool;
|
||||
|
||||
user_check!(new_pw.login());
|
||||
|
||||
let pool = extract(|data: Data<AppData>| async move { data.db_pool().clone() }).await?;
|
||||
let pool = get_pool().await?;
|
||||
let usr = user_from_login(&pool, new_pw.login()).await?;
|
||||
let user = logged_in_user().await.unwrap_or(User::default());
|
||||
|
||||
if !pwhash::bcrypt::verify(new_pw.old_password(), &usr.password) {
|
||||
if (!user.admin || user.login == new_pw.login())
|
||||
&& !pwhash::bcrypt::verify(new_pw.old_password(), &usr.password) {
|
||||
let response = expect_context::<ResponseOptions>();
|
||||
response.set_status(StatusCode::UNAUTHORIZED);
|
||||
|
||||
@@ -191,4 +196,68 @@ impl ForValidation for ChangePwd {
|
||||
fn entity(&self) -> &dyn Validate {
|
||||
&self.new_pw
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn create_user(user: UserProfile) -> Result<ApiResponse<()>, ServerFnError> {
|
||||
use crate::perm_check;
|
||||
use crate::backend::get_pool;
|
||||
|
||||
perm_check!(is_admin);
|
||||
|
||||
let pool = get_pool().await?;
|
||||
let count: (i64,) = sqlx::query_as(r#"SELECT COUNT(id) FROM "user" WHERE login = $1"#)
|
||||
.bind(user.login())
|
||||
.fetch_one(&pool)
|
||||
.await?;
|
||||
|
||||
if count.0 != 0 {
|
||||
let response = expect_context::<ResponseOptions>();
|
||||
response.set_status(StatusCode::CONFLICT);
|
||||
|
||||
return Ok(ApiResponse::Error("Username already exists".to_string()));
|
||||
}
|
||||
|
||||
let usr_pw = user.password().clone();
|
||||
|
||||
sqlx::query(r#"INSERT INTO "user"(login, password, full_name, email, admin, get_emails) VALUES($1, $2, $3, $4, $5, $6)"#)
|
||||
.bind(user.login())
|
||||
.bind(pwhash::bcrypt::hash(usr_pw.unwrap_or("".to_string())).unwrap())
|
||||
.bind(user.full_name())
|
||||
.bind(user.email())
|
||||
.bind(user.admin())
|
||||
.bind(user.get_emails())
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
Ok(ApiResponse::Data(()))
|
||||
}
|
||||
|
||||
impl ForValidation for CreateUser {
|
||||
fn entity(&self) -> &dyn Validate {
|
||||
&self.user
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn delete_user(id: i32) -> Result<ApiResponse<()>, ServerFnError> {
|
||||
use crate::perm_check;
|
||||
use crate::backend::get_pool;
|
||||
|
||||
perm_check!(is_admin);
|
||||
let user = logged_in_user().await.unwrap_or_default();
|
||||
|
||||
if user.id() == id {
|
||||
let response = expect_context::<ResponseOptions>();
|
||||
response.set_status(StatusCode::NOT_ACCEPTABLE);
|
||||
|
||||
return Ok(ApiResponse::Error("You can't delete yourself".to_string()))
|
||||
}
|
||||
|
||||
sqlx::query(r#"DELETE FROM "user" WHERE id=$1"#)
|
||||
.bind(id)
|
||||
.execute(&get_pool().await?)
|
||||
.await?;
|
||||
|
||||
Ok(ApiResponse::Data(()))
|
||||
}
|
||||
@@ -39,7 +39,9 @@ pub fn data_form<T: 'static + server_fn::ServerFn<()> + Clone + ForValidation>(
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"
|
||||
on:click=move |_| {validator.reset(); opener.hide()}>
|
||||
on:click=move |_| {
|
||||
validator.reset();
|
||||
opener.hide();}>
|
||||
{trl("Close")}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
@@ -50,3 +52,34 @@ pub fn data_form<T: 'static + server_fn::ServerFn<()> + Clone + ForValidation>(
|
||||
</ActionForm>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn question_dialog<T: 'static + server_fn::ServerFn<()> + Clone>(
|
||||
opener: DialogOpener,
|
||||
action: Action<T, Result<ApiResponse<()>, ServerFnError>>,
|
||||
title: &'static str,
|
||||
children: Children
|
||||
) -> impl IntoView {
|
||||
let upd_val = action.value();
|
||||
|
||||
view! {
|
||||
<ActionForm action=action>
|
||||
<ModalDialog opener=opener title=title>
|
||||
<ModalBody>
|
||||
<ServerErr result={upd_val} opener=opener/>
|
||||
{children()}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"
|
||||
on:click=move |_| {
|
||||
opener.hide();}>
|
||||
{trl("No")}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{trl("Yes")}
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</ModalDialog>
|
||||
</ActionForm>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ pub fn Header() -> impl IntoView {
|
||||
<Html
|
||||
lang="cz"
|
||||
dir="ltr"
|
||||
attributes=AdditionalAttributes::from(vec![
|
||||
("data-theme", "theme-default"),
|
||||
("class", "light-style layout-menu-fixed"),
|
||||
("data-template", "vertical-menu-template-free"),
|
||||
("data-assets-path", "/")])
|
||||
attributes=vec![
|
||||
("data-theme", "theme-default".into_attribute()),
|
||||
("class", "light-style layout-menu-fixed".into_attribute()),
|
||||
("data-template", "vertical-menu-template-free".into_attribute()),
|
||||
("data-assets-path", "/".into_attribute())]
|
||||
/>
|
||||
<Title text="Rezervovator"/>
|
||||
<Meta charset="utf-8"/>
|
||||
|
||||
@@ -5,14 +5,27 @@ use leptos::*;
|
||||
pub struct DialogOpener {
|
||||
visible: ReadSignal<bool>,
|
||||
set_visible: WriteSignal<bool>,
|
||||
empty: ReadSignal<String>,
|
||||
set_empty: WriteSignal<String>,
|
||||
not_checked: ReadSignal<Option<String>>,
|
||||
set_not_checked: WriteSignal<Option<String>>,
|
||||
show_err: RwSignal<bool>
|
||||
}
|
||||
|
||||
impl DialogOpener {
|
||||
pub fn new() -> Self {
|
||||
let (visible, set_visible) = create_signal(false);
|
||||
let (empty, set_empty) = create_signal("".to_string());
|
||||
let (not_checked, set_not_checked) = create_signal(None);
|
||||
let show_err = create_rw_signal(false);
|
||||
DialogOpener {
|
||||
visible,
|
||||
set_visible,
|
||||
empty,
|
||||
set_empty,
|
||||
not_checked,
|
||||
set_not_checked,
|
||||
show_err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +39,25 @@ impl DialogOpener {
|
||||
|
||||
pub fn hide(&self) {
|
||||
self.set_visible.update(|state| *state = false);
|
||||
self.set_empty.set("".to_string());
|
||||
self.set_not_checked.set(None);
|
||||
self.show_err.set(false);
|
||||
}
|
||||
|
||||
pub fn empty(&self) -> String {
|
||||
self.empty.get()
|
||||
}
|
||||
|
||||
pub fn not_checked(&self) -> Option<String> {
|
||||
self.not_checked.get()
|
||||
}
|
||||
|
||||
pub fn show_err(&self) -> bool {
|
||||
self.show_err.get()
|
||||
}
|
||||
|
||||
pub fn display_err(&self) {
|
||||
self.show_err.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,15 @@ pub fn ServerErr(
|
||||
if let Some(val) = result.get() {
|
||||
match val {
|
||||
Ok(resp) => if let ApiResponse::Error(err) = resp {
|
||||
opener.display_err();
|
||||
view! {
|
||||
<div class="alert alert-danger">
|
||||
<div class="alert alert-danger" style={move || {
|
||||
if opener.show_err() {
|
||||
""
|
||||
} else {
|
||||
"display: none"
|
||||
}
|
||||
}}>
|
||||
{trl(&err)}
|
||||
</div>
|
||||
}
|
||||
@@ -30,16 +37,6 @@ pub fn ServerErr(
|
||||
}
|
||||
}
|
||||
}
|
||||
/*if let Err(e) = val {
|
||||
view! {
|
||||
<div class="alert alert-danger">
|
||||
"Server error: " {e.to_string()}
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
opener.hide();
|
||||
view! {<div></div>}
|
||||
}*/
|
||||
} else {
|
||||
view! {<div></div>}
|
||||
}
|
||||
|
||||
+81
-45
@@ -1,10 +1,11 @@
|
||||
use leptos::*;
|
||||
use crate::app::MenuHelper;
|
||||
use crate::backend::data::User;
|
||||
use crate::backend::user::{get_user, logout};
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
use crate::locales::trl;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct MenuOpener {
|
||||
visible: ReadSignal<bool>,
|
||||
set_visible: WriteSignal<bool>,
|
||||
@@ -23,9 +24,23 @@ impl MenuOpener {
|
||||
self.visible.get()
|
||||
}
|
||||
|
||||
pub fn toggle(&self) {
|
||||
pub fn toggle(self) {
|
||||
let visible = self.visible.get();
|
||||
self.set_visible.update(|v| *v = !visible)
|
||||
self.set_visible.update(|v| *v = !visible);
|
||||
|
||||
let helper = use_context::<MenuHelper>().expect("No menu helper");
|
||||
if !visible {
|
||||
helper.close();
|
||||
helper.set_opened(self);
|
||||
} else {
|
||||
helper.reset();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
self.set_visible.set(false);
|
||||
let helper = use_context::<MenuHelper>().expect("No menu helper");
|
||||
helper.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,55 +53,76 @@ pub fn UserMenu(
|
||||
let user = create_resource(move || opener.visible(), move |_| get_user());
|
||||
|
||||
view! {
|
||||
<ul class={move || if opener.visible() {"dropdown-menu dropdown-menu-end show"} else
|
||||
{"dropdown-menu dropdown-menu-end"}}
|
||||
data-bs-popper="none">
|
||||
<ul
|
||||
class=move || {
|
||||
if opener.visible() {
|
||||
"dropdown-menu dropdown-menu-end show"
|
||||
} else {
|
||||
"dropdown-menu dropdown-menu-end"
|
||||
}
|
||||
}
|
||||
data-bs-popper="none"
|
||||
on:mouseleave=move |_| opener.close()
|
||||
>
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" on:click=move |_| {editor.show(); opener.toggle()}>
|
||||
<div class="d-flex">
|
||||
<div class="flex-shrink-0 me-3">
|
||||
<i class="bx bxs-user-account" />
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<Suspense fallback=move || view! {<span>"Loading..."</span>}>
|
||||
{move || {
|
||||
user.get().map(|u| match u {
|
||||
Ok(user) => {
|
||||
let usr = user.unwrap_or(User::default());
|
||||
user_profile.update(|u| *u = usr.clone());
|
||||
view! {
|
||||
<span class="fw-semibold d-block">
|
||||
{usr.full_name.unwrap_or("".to_string())}
|
||||
</span>
|
||||
//<small class="text-muted">"Admin"</small>
|
||||
}},
|
||||
Err(_) => view! {<span>"Error loading user"</span>}
|
||||
})
|
||||
}}
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" on:click=move |_| { editor.show() }>
|
||||
<div class="d-flex">
|
||||
<div class="flex-shrink-0 me-3">
|
||||
<i class="bx bxs-user-account"></i>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<Suspense fallback=move || {
|
||||
view! { <span>"Loading..."</span> }
|
||||
}>
|
||||
{move || {
|
||||
user.get()
|
||||
.map(|u| match u {
|
||||
Ok(user) => {
|
||||
let usr = user.unwrap_or(User::default());
|
||||
user_profile.update(|u| *u = usr.clone());
|
||||
view! {
|
||||
<span class="fw-semibold d-block">
|
||||
{usr.full_name}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
view! {
|
||||
// <small class="text-muted">"Admin"</small>
|
||||
<span>"Error loading user"</span>
|
||||
}
|
||||
}
|
||||
})
|
||||
}}
|
||||
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" on:click=move |_| {pw_dialog.show(); opener.toggle()}>
|
||||
<i class="bx bx-lock me-2"></i>
|
||||
<span class="align-middle">{trl("Change password")}</span>
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" on:click=move |_| { pw_dialog.show() }>
|
||||
<i class="bx bx-lock me-2"></i>
|
||||
<span class="align-middle">{trl("Change password")}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="dropdown-divider"></div>
|
||||
<div class="dropdown-divider"></div>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/login" on:click=move |_| {
|
||||
spawn_local(async move {
|
||||
let _ = logout().await;
|
||||
});
|
||||
}>
|
||||
<i class="bx bx-power-off me-2"></i>
|
||||
<span class="align-middle">{trl("Log Out")}</span>
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="/login"
|
||||
on:click=move |_| {
|
||||
spawn_local(async move {
|
||||
let _ = logout().await;
|
||||
});
|
||||
}
|
||||
>
|
||||
<i class="bx bx-power-off me-2"></i>
|
||||
<span class="align-middle">{trl("Log Out")}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
+53
-51
@@ -1,62 +1,64 @@
|
||||
use leptos::*;
|
||||
use crate::backend::data::{ApiResponse, User};
|
||||
use crate::backend::user::ChangePwd;
|
||||
use crate::backend::data::User;
|
||||
use crate::backend::user::{ChangePwd, get_user};
|
||||
use crate::components::data_form::DataForm;
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
|
||||
#[component]
|
||||
pub fn change_password(user: ReadSignal<User>, opener: DialogOpener) -> impl IntoView {
|
||||
let change_pwd = create_server_action::<ChangePwd>();
|
||||
let empty = create_rw_signal("".to_string());
|
||||
let logged_in = create_resource(||(), move |_| get_user());
|
||||
|
||||
view! {
|
||||
{move || {
|
||||
if let Some(res) = change_pwd.value().get() {
|
||||
if let Ok(r) = res {
|
||||
if let ApiResponse::Data(_) = r { empty.update(|e| *e = "".to_string())}
|
||||
}
|
||||
}
|
||||
view! {
|
||||
<DataForm opener=opener action=change_pwd title="Change password">
|
||||
<input type="hidden" value={move || user.get().login} name="new_pw[login]"/>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="oldPw" class="form-label">"Old password"</label>
|
||||
<input
|
||||
type="password"
|
||||
id="oldPw"
|
||||
class="form-control"
|
||||
name="new_pw[old_password]"
|
||||
prop:value={move || empty.get()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="newPw" class="form-label">"New password"</label>
|
||||
<input
|
||||
type="password"
|
||||
id="newPw"
|
||||
class="form-control"
|
||||
name="new_pw[password]"
|
||||
prop:value={move || empty.get()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="verPw" class="form-label">"Verify password"</label>
|
||||
<input
|
||||
type="password"
|
||||
id="verPw"
|
||||
class="form-control"
|
||||
name="new_pw[password_ver]"
|
||||
prop:value={move || empty.get()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DataForm>
|
||||
}
|
||||
}}
|
||||
<DataForm opener=opener action=change_pwd title="Change password">
|
||||
<input type="hidden" value={move || user.get().login} name="new_pw[login]"/>
|
||||
<Suspense fallback=move || view! {<div></div>}>
|
||||
<div class="row">
|
||||
<div class="col mb-3" style={move || {
|
||||
logged_in.get().map(|u| { match u {
|
||||
Ok(u) => {
|
||||
if let Some(u) = u {
|
||||
if u.login != user.get().login && u.admin { "display: none" } else { "" }
|
||||
} else { "" }
|
||||
}
|
||||
Err(_) => ""
|
||||
}})
|
||||
}}>
|
||||
<label for="oldPw" class="form-label">"Old password"</label>
|
||||
<input
|
||||
type="password"
|
||||
id="oldPw"
|
||||
class="form-control"
|
||||
name="new_pw[old_password]"
|
||||
prop:value={move || opener.empty()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Suspense>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="newPw" class="form-label">"New password"</label>
|
||||
<input
|
||||
type="password"
|
||||
id="newPw"
|
||||
class="form-control"
|
||||
name="new_pw[password]"
|
||||
prop:value={move || opener.empty()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="verPw" class="form-label">"Verify password"</label>
|
||||
<input
|
||||
type="password"
|
||||
id="verPw"
|
||||
class="form-control"
|
||||
name="new_pw[password_ver]"
|
||||
prop:value={move || opener.empty()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DataForm>
|
||||
}
|
||||
}
|
||||
@@ -8,3 +8,4 @@ pub mod profile_edit;
|
||||
pub mod change_pwd;
|
||||
pub mod users;
|
||||
pub mod user_edit;
|
||||
pub mod user_delete;
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
use leptos::*;
|
||||
use crate::backend::data::User;
|
||||
use crate::backend::user::UpdateProfile;
|
||||
use crate::backend::user::{get_user, UpdateProfile};
|
||||
use crate::components::data_form::DataForm;
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
|
||||
#[component]
|
||||
pub fn ProfileEdit(user: ReadSignal<User>, opener: DialogOpener) -> impl IntoView {
|
||||
let update_user = create_server_action::<UpdateProfile>();
|
||||
let logged_in = create_resource(||(), move |_| get_user());
|
||||
|
||||
view! {
|
||||
<DataForm opener=opener action=update_user title="Edit profile">
|
||||
<input type="hidden" value={move || user.get().login} name="user[login]"/>
|
||||
<input type="hidden" prop:value={move || user.get().login} name="user[login]"/>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="name" class="form-label">"Full name"</label>
|
||||
@@ -50,6 +51,31 @@ pub fn ProfileEdit(user: ReadSignal<User>, opener: DialogOpener) -> impl IntoVie
|
||||
<label class="form-check-label" for="getMail">"Get emails"</label>
|
||||
</div>
|
||||
</div>
|
||||
<Suspense fallback=move || view! {<div></div>}>
|
||||
<div class="row" style={move || {
|
||||
logged_in.get().map(|u| match u {
|
||||
Ok(usr) => {
|
||||
let usr = usr.unwrap_or_default();
|
||||
if usr.login == user.get().login && !usr.login.is_empty() { "display: none" }
|
||||
else {""}
|
||||
}
|
||||
Err(_) => ""
|
||||
})
|
||||
} }>
|
||||
<div class="col mb-3">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="admin"
|
||||
prop:value={move || if user.get().admin {"true"} else {"false"}}
|
||||
prop:checked={move || user.get().admin}
|
||||
name="user[admin]"
|
||||
//disabled
|
||||
/>
|
||||
<label class="form-check-label" for="admin">"Admin"</label>
|
||||
</div>
|
||||
</div>
|
||||
</Suspense>
|
||||
</DataForm>
|
||||
}
|
||||
}
|
||||
+4
-30
@@ -1,44 +1,18 @@
|
||||
use leptos::*;
|
||||
use crate::locales::trl;
|
||||
use crate::pages::company_info::CompanyInfo;
|
||||
use crate::pages::users::Users;
|
||||
|
||||
#[component]
|
||||
pub fn Settings() -> impl IntoView {
|
||||
view! {
|
||||
<h1>{trl("Settings")}</h1>
|
||||
<div class="row mb-5">
|
||||
<div class="col-md-6 col-lg-4 mb-3">
|
||||
<div class="col-md">
|
||||
<CompanyInfo/>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-4 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">"Card title"</h5>
|
||||
<h6 class="card-subtitle text-muted">"Support card subtitle"</h6>
|
||||
</div>
|
||||
<img class="img-fluid" src="../assets/img/elements/13.jpg" alt="Card image cap" />
|
||||
<div class="card-body">
|
||||
<p class="card-text">"Bear claw sesame snaps gummies chocolate."</p>
|
||||
<a href="javascript:void(0);" class="card-link">"Card link"</a>
|
||||
<a href="javascript:void(0);" class="card-link">"Another link"</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-4 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">"Card title"</h5>
|
||||
<h6 class="card-subtitle text-muted">"Support card subtitle"</h6>
|
||||
<img
|
||||
class="img-fluid d-flex mx-auto my-4"
|
||||
src="../assets/img/elements/4.jpg"
|
||||
alt="Card image cap"
|
||||
/>
|
||||
<p class="card-text">"Bear claw sesame snaps gummies chocolate."</p>
|
||||
<a href="javascript:void(0);" class="card-link">Card link</a>
|
||||
<a href="javascript:void(0);" class="card-link">Another link</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<Users/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
use leptos::*;
|
||||
use crate::backend::data::User;
|
||||
use crate::backend::user::DeleteUser;
|
||||
use crate::components::data_form::QuestionDialog;
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
|
||||
#[component]
|
||||
pub fn user_delete(user: ReadSignal<User>, opener: DialogOpener) -> impl IntoView {
|
||||
let del_user = create_server_action::<DeleteUser>();
|
||||
|
||||
view! {
|
||||
<QuestionDialog opener=opener action=del_user title="Delete user">
|
||||
<input type="hidden" prop:value={move || user.get().id()} name="id"/>
|
||||
<div>"Are you sure you want to delete user "{move || user.get().full_name}"?"</div>
|
||||
</QuestionDialog>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
use leptos::*;
|
||||
use crate::backend::user::CreateUser;
|
||||
use crate::components::data_form::DataForm;
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
|
||||
#[component]
|
||||
pub fn user_edit(opener: DialogOpener) -> impl IntoView {
|
||||
let create_usr = create_server_action::<CreateUser>();
|
||||
view! {
|
||||
<DataForm opener=opener action=create_usr title="Create user">
|
||||
//<input type="hidden" value={move || company.get().id()} name="company[id]"/>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="username" class="form-label">"Username"</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
class="form-control"
|
||||
placeholder="Enter username"
|
||||
prop:value={move || opener.empty()}
|
||||
name="user[login]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="password" class="form-label">"Password"</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
class="form-control"
|
||||
prop:value={move || opener.empty()}
|
||||
name="user[password]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="passwordVer" class="form-label">"Verify password"</label>
|
||||
<input
|
||||
type="password"
|
||||
id="passwordVer"
|
||||
class="form-control"
|
||||
prop:value={move || opener.empty()}
|
||||
name="user[password_ver]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="fullName" class="form-label">"Full name"</label>
|
||||
<input
|
||||
type="text"
|
||||
id="fullName"
|
||||
class="form-control"
|
||||
placeholder="Enter full name"
|
||||
prop:value={move || opener.empty()}
|
||||
name="user[full_name]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="email" class="form-label">"Email"</label>
|
||||
<input
|
||||
type="text"
|
||||
id="email"
|
||||
class="form-control"
|
||||
placeholder="Enter email"
|
||||
prop:value={move || opener.empty()}
|
||||
name="user[email]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="admin"
|
||||
class="form-check-input"
|
||||
prop:checked={move || opener.not_checked()}
|
||||
name="user[admin]"
|
||||
/>
|
||||
<label for="admin" class="form-label">"Admin"</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="getEmails"
|
||||
class="form-check-input"
|
||||
prop:checked={move || opener.not_checked()}
|
||||
name="user[get_emails]"
|
||||
/>
|
||||
<label for="geEmails" class="form-label">"Get emails"</label>
|
||||
</div>
|
||||
</div>
|
||||
</DataForm>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
use leptos::*;
|
||||
use crate::backend::data::{ApiResponse, User};
|
||||
use crate::backend::user::get_users;
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
use crate::components::user_menu::MenuOpener;
|
||||
use crate::locales::trl;
|
||||
use crate::pages::change_pwd::ChangePassword;
|
||||
use crate::pages::profile_edit::ProfileEdit;
|
||||
use crate::pages::user_delete::UserDelete;
|
||||
use crate::pages::user_edit::UserEdit;
|
||||
|
||||
#[component]
|
||||
pub fn users() -> impl IntoView {
|
||||
let editor = DialogOpener::new();
|
||||
let profile_editor = DialogOpener::new();
|
||||
let pwd_dialog = DialogOpener::new();
|
||||
let delete_dialog = DialogOpener::new();
|
||||
let users = create_blocking_resource(
|
||||
move || editor.visible() || profile_editor.visible() || delete_dialog.visible(), move |_| {get_users()});
|
||||
let (usr, set_usr) = create_signal::<Vec<User>>(vec![]);
|
||||
let (profile, set_profile) = create_signal(User::default());
|
||||
|
||||
view! {
|
||||
<UserEdit opener=editor/>
|
||||
<ProfileEdit user=profile opener=profile_editor/>
|
||||
<ChangePassword opener=pwd_dialog user=profile/>
|
||||
<UserDelete opener=delete_dialog user=profile/>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="bx bx-user"></i>" "{trl("Users")}</h5>
|
||||
<Transition fallback=move || view! {<p>{trl("Loading...")}</p> }>
|
||||
<table class="table card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{trl("Username")}</th>
|
||||
<th>{trl("Full name")}</th>
|
||||
<th>{trl("Admin")}</th>
|
||||
<th>{trl("Actions")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{move || {
|
||||
users.get().map(|u| match u {
|
||||
Err(e) => {
|
||||
let err = if e.to_string().contains("403") {
|
||||
"Only admin can edit users".to_string()
|
||||
} else {
|
||||
e.to_string()
|
||||
};
|
||||
view! {<tbody class="table-border-bottom-0">
|
||||
<tr><td colspan=4>{trl(&err)}</td></tr></tbody>}}
|
||||
Ok(u) => {
|
||||
match u {
|
||||
ApiResponse::Data(u) => {
|
||||
set_usr.update(|users| *users = u.clone());
|
||||
view! {<tbody class="table-border-bottom-0">
|
||||
<For each=move || usr.get()
|
||||
key=|user| user.id()
|
||||
children=move |user: User| {
|
||||
let menu = MenuOpener::new();
|
||||
let user_profile = user.clone();
|
||||
let user_passwd = user.clone();
|
||||
let user_delete = user.clone();
|
||||
view! {
|
||||
<tr>
|
||||
<td>{&user.login}</td>
|
||||
<td>{&user.full_name.unwrap_or("".to_string())}</td>
|
||||
<td>{if user.admin {view! {<i class="bx bx-check"></i>}}
|
||||
else {view! {<i></i>}}}</td>
|
||||
<td>
|
||||
<div class="dropdown">
|
||||
<button type="button" class="btn p-0 dropdown-toggle hide-arrow"
|
||||
on:click=move |_| menu.toggle()>
|
||||
<i class="bx bx-dots-vertical-rounded"></i>
|
||||
</button>
|
||||
<div class={move || if menu.visible() {"dropdown-menu show"} else {"dropdown-menu"} }
|
||||
style="position: absolute; insert: 0px 0px auto; margin: 0px; transform: translate3d(-160px, 0px, 0px);"
|
||||
on:mouseleave=move |_| menu.toggle()>
|
||||
<a class="dropdown-item" href="javascript:void(0);" on:click=move |_| {
|
||||
set_profile.set(user_profile.clone());
|
||||
profile_editor.show();
|
||||
}>
|
||||
<i class="bx bx-edit-alt me-1"></i> {trl("Edit")}</a>
|
||||
<a class="dropdown-item" href="javascript:void(0);" on:click=move |_| {
|
||||
set_profile.set(user_passwd.clone());
|
||||
pwd_dialog.show();
|
||||
}>
|
||||
<i class="bx bx-lock me-1"></i> {trl("Change password")}</a>
|
||||
<a class="dropdown-item" href="javascript:void(0);" on:click=move |_| {
|
||||
set_profile.set(user_delete.clone());
|
||||
delete_dialog.show();
|
||||
}>
|
||||
<i class="bx bx-trash me-1"></i> {trl("Delete")}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}/></tbody>
|
||||
}
|
||||
}
|
||||
ApiResponse::Error(e) => {view! {<tbody class="table-border-bottom-0">
|
||||
<tr><td colspan=4>{trl(&e)}</td></tr></tbody>}}
|
||||
}
|
||||
}
|
||||
})
|
||||
}}
|
||||
</table>
|
||||
</Transition>
|
||||
<a href="#" class="card-link" on:click=move |_| editor.show()>
|
||||
<i class="bx bx-plus-circle fs-4 lh-0"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user