diff --git a/Dioxus.toml b/Dioxus.toml index da58640..5ce9802 100644 --- a/Dioxus.toml +++ b/Dioxus.toml @@ -12,7 +12,7 @@ reload_html = true watch_path = ["src", "assets"] [web.resource] -style = ["/assets/style/main.css"] +style = [] script = [] [web.resource.dev] diff --git a/assets/admin/style/main.css b/assets/admin/style/main.css new file mode 100644 index 0000000..6041100 --- /dev/null +++ b/assets/admin/style/main.css @@ -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; +} \ No newline at end of file diff --git a/assets/style/main.css b/assets/renderer/style/main.css similarity index 100% rename from assets/style/main.css rename to assets/renderer/style/main.css diff --git a/src/inbound/renderer/admin_ui/mod.rs b/src/inbound/renderer/admin_ui/mod.rs index 8b6997d..bb4b1e3 100644 --- a/src/inbound/renderer/admin_ui/mod.rs +++ b/src/inbound/renderer/admin_ui/mod.rs @@ -2,12 +2,16 @@ use dioxus::prelude::*; mod server; -#[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Routable, Debug, PartialEq)] #[rustfmt::skip] enum Route { #[layout(AdminLayout)] #[route("/")] Home {}, + + #[nest("/site")] + #[route("/:domain")] + Site { domain: String }, } pub fn App() -> Element { @@ -16,24 +20,33 @@ pub fn App() -> Element { } } +#[component] fn Home() -> Element { rsx! { - h2 { "Hello!" } SuspenseBoundary { fallback: |_context: SuspenseContext| rsx! { "Loading sites..." }, SiteList {} } } } +#[component] fn SiteList() -> Element { let sites = use_server_future(server::site_list)?.suspend()?; rsx! { h3 { "Sites" } + button { "New site" } match &*sites.read() { Ok(sites) => { rsx! { ul { 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 { 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! { "..." }, main { Outlet::<Route> {} } } @@ -69,7 +108,7 @@ mod tests { fn site_list_shows_sites() { let mut app = VirtualDom::new(|| { rsx! { - SiteList {} + Router::<Route> {} } }); diff --git a/src/inbound/renderer/admin_ui/server.rs b/src/inbound/renderer/admin_ui/server.rs index 10739ce..d064e76 100644 --- a/src/inbound/renderer/admin_ui/server.rs +++ b/src/inbound/renderer/admin_ui/server.rs @@ -18,3 +18,17 @@ pub async fn site_list() -> Result<Vec<SiteInfo>, ServerFnError> { }) .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![], + }) +} diff --git a/src/inbound/renderer/mod.rs b/src/inbound/renderer/mod.rs index ec4f213..b21a052 100644 --- a/src/inbound/renderer/mod.rs +++ b/src/inbound/renderer/mod.rs @@ -15,7 +15,7 @@ mod server; #[cfg(test)] mod testing; -#[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Routable, Debug, PartialEq)] #[rustfmt::skip] enum Route { #[layout(SiteLayout)] @@ -31,6 +31,7 @@ enum Route { PageNotFound { route: Vec<String> }, } +#[component] pub fn App() -> Element { // Check for special redirects 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] fn SiteLayout() -> Element { let site = meta::site(); let route = use_route::<Route>(); rsx! { + document::Stylesheet { href: CSS } header { class: "site-header", nav { class: "page-list", 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 { admin_host: "admin.local".to_string(), @@ -337,6 +355,6 @@ mod tests { app.rebuild_in_place(); let elem_str = dioxus::ssr::render(&app); println!("elem_str: {elem_str}"); - assert!(elem_str.contains("Admin UI")); + assert!(elem_str.contains("admin-ui")); } }