start work on admin cp

This commit is contained in:
Hamcha 2025-02-09 14:13:34 +01:00
parent 2941690f0a
commit bd32c02e2c
Signed by: hamcha
GPG key ID: 1669C533B8CF6D89
6 changed files with 94 additions and 8 deletions
Dioxus.toml
assets
admin/style
renderer/style
src/inbound/renderer

View file

@ -12,7 +12,7 @@ reload_html = true
watch_path = ["src", "assets"] watch_path = ["src", "assets"]
[web.resource] [web.resource]
style = ["/assets/style/main.css"] style = []
script = [] script = []
[web.resource.dev] [web.resource.dev]

View file

@ -0,0 +1,15 @@
@import url(https://rsms.me/inter/inter.css);
:root {
background-color: #312337;
color: #fff6fe;
font-family: Inter, sans-serif;
/* fix for Chrome */
font-feature-settings: 'liga' 1, 'calt' 1;
}
a,
a:visited {
color: #f0cf96;
}

View file

@ -2,12 +2,16 @@ use dioxus::prelude::*;
mod server; mod server;
#[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Clone, Routable, Debug, PartialEq)]
#[rustfmt::skip] #[rustfmt::skip]
enum Route { enum Route {
#[layout(AdminLayout)] #[layout(AdminLayout)]
#[route("/")] #[route("/")]
Home {}, Home {},
#[nest("/site")]
#[route("/:domain")]
Site { domain: String },
} }
pub fn App() -> Element { pub fn App() -> Element {
@ -16,24 +20,33 @@ pub fn App() -> Element {
} }
} }
#[component]
fn Home() -> Element { fn Home() -> Element {
rsx! { rsx! {
h2 { "Hello!" }
SuspenseBoundary { fallback: |_context: SuspenseContext| rsx! { "Loading sites..." }, SiteList {} } SuspenseBoundary { fallback: |_context: SuspenseContext| rsx! { "Loading sites..." }, SiteList {} }
} }
} }
#[component]
fn SiteList() -> Element { fn SiteList() -> Element {
let sites = use_server_future(server::site_list)?.suspend()?; let sites = use_server_future(server::site_list)?.suspend()?;
rsx! { rsx! {
h3 { "Sites" } h3 { "Sites" }
button { "New site" }
match &*sites.read() { match &*sites.read() {
Ok(sites) => { Ok(sites) => {
rsx! { rsx! {
ul { ul {
for site in sites { for site in sites {
li { "{site.domain} - {site.title}" } li {
Link {
to: Route::Site {
domain: site.domain.clone(),
},
"{site.domain} - {site.title}"
}
}
} }
} }
} }
@ -47,9 +60,35 @@ fn SiteList() -> Element {
} }
} }
#[component]
fn Site(domain: String) -> Element {
let site = use_server_future(move || server::get_site(domain.clone()))?.suspend()?;
let info = match &*site.read() {
Ok(site) => site.clone(),
Err(err) => {
return rsx! {
h1 { "Error: {err}" }
}
}
};
rsx! {
h1 { "{info.title}" }
}
}
static CSS: Asset = asset!("/assets/admin/style/main.css");
#[component]
fn AdminLayout() -> Element { fn AdminLayout() -> Element {
rsx! { rsx! {
h1 { "Admin UI" } document::Stylesheet { href: CSS }
header { "data-test-id": "admin-ui",
div {
Link { to: Route::Home {}, "Administration panel" }
}
}
SuspenseBoundary { fallback: |_context: SuspenseContext| rsx! { "..." }, SuspenseBoundary { fallback: |_context: SuspenseContext| rsx! { "..." },
main { Outlet::<Route> {} } main { Outlet::<Route> {} }
} }
@ -69,7 +108,7 @@ mod tests {
fn site_list_shows_sites() { fn site_list_shows_sites() {
let mut app = VirtualDom::new(|| { let mut app = VirtualDom::new(|| {
rsx! { rsx! {
SiteList {} Router::<Route> {}
} }
}); });

View file

@ -18,3 +18,17 @@ pub async fn site_list() -> Result<Vec<SiteInfo>, ServerFnError> {
}) })
.collect()) .collect())
} }
#[server]
pub async fn get_site(domain: String) -> Result<SiteInfo, ServerFnError> {
use crate::outbound::services::site::SiteServiceProvider;
let FromContext(SiteServiceProvider { service }) = extract().await?;
let site = service.get_site(&domain).await?;
Ok(SiteInfo {
domain: site.domain.clone(),
title: site.title.clone(),
pages: vec![],
})
}

View file

@ -15,7 +15,7 @@ mod server;
#[cfg(test)] #[cfg(test)]
mod testing; mod testing;
#[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Clone, Routable, Debug, PartialEq)]
#[rustfmt::skip] #[rustfmt::skip]
enum Route { enum Route {
#[layout(SiteLayout)] #[layout(SiteLayout)]
@ -31,6 +31,7 @@ enum Route {
PageNotFound { route: Vec<String> }, PageNotFound { route: Vec<String> },
} }
#[component]
pub fn App() -> Element { pub fn App() -> Element {
// Check for special redirects // Check for special redirects
let site_res = use_server_future(server::get_site_info)?.suspend()?; let site_res = use_server_future(server::get_site_info)?.suspend()?;
@ -79,12 +80,15 @@ fn PageLink(page: String, title: String, current: Route) -> Element {
} }
} }
static CSS: Asset = asset!("/assets/renderer/style/main.css");
#[component] #[component]
fn SiteLayout() -> Element { fn SiteLayout() -> Element {
let site = meta::site(); let site = meta::site();
let route = use_route::<Route>(); let route = use_route::<Route>();
rsx! { rsx! {
document::Stylesheet { href: CSS }
header { class: "site-header", header { class: "site-header",
nav { class: "page-list", nav { class: "page-list",
ul { ul {
@ -228,6 +232,20 @@ mod tests {
}) })
}) })
}); });
mock_service.expect_get_page().times(1).returning(|_, _| {
Box::pin(async {
Ok(entities::site::Page {
info: entities::site::PageInfo {
title: "Test page".to_string(),
name: "/".to_string(),
order: 0,
},
content: PageContent::Single {
content: entities::site::Post { blocks: vec![] },
},
})
})
});
server_context().insert(AppConfig { server_context().insert(AppConfig {
admin_host: "admin.local".to_string(), admin_host: "admin.local".to_string(),
@ -337,6 +355,6 @@ mod tests {
app.rebuild_in_place(); app.rebuild_in_place();
let elem_str = dioxus::ssr::render(&app); let elem_str = dioxus::ssr::render(&app);
println!("elem_str: {elem_str}"); println!("elem_str: {elem_str}");
assert!(elem_str.contains("Admin UI")); assert!(elem_str.contains("admin-ui"));
} }
} }