Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

frontend: rework error boundary #663

Merged
merged 8 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion nghe-api/src/user/info.rs → nghe-api/src/user/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use nghe_proc_macro::api_derive;
use super::Role;

#[api_derive(fake = true)]
#[endpoint(path = "userInfo", internal = true)]
#[endpoint(path = "getUser", internal = true)]
pub struct Request;

#[api_derive]
Expand Down
2 changes: 1 addition & 1 deletion nghe-api/src/user/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub mod create;
pub mod info;
pub mod get;
mod role;
pub mod setup;

Expand Down
2 changes: 1 addition & 1 deletion nghe-backend/src/orm/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub struct UsernameAuthentication<'a> {

#[derive(Debug, Queryable, Selectable, Insertable, o2o)]
#[diesel(table_name = users, check_for_backend(super::Type))]
#[owned_into(nghe_api::user::info::Response)]
#[owned_into(nghe_api::user::get::Response)]
pub struct Info<'a> {
#[into(~.into_owned())]
pub username: Cow<'a, str>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use diesel::{ExpressionMethods, QueryDsl, SelectableHelper};
use diesel_async::RunQueryDsl;
pub use nghe_api::user::info::{Request, Response};
pub use nghe_api::user::get::{Request, Response};
use nghe_proc_macro::handler;
use uuid::Uuid;

Expand Down
4 changes: 2 additions & 2 deletions nghe-backend/src/route/user/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
pub mod create;
mod info;
mod get;
mod setup;

nghe_proc_macro::build_router! {
modules = [
create(internal = true),
info(internal = true),
get(internal = true),
setup(internal = true),
],
}
2 changes: 1 addition & 1 deletion nghe-frontend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ workspace = true

[dependencies]
concat-string = { workspace = true }
thiserror = { workspace = true }
uuid = { workspace = true }

anyhow = { version = "1.0.95" }
codee = { version = "0.2.0" }
console_error_panic_hook = { version = "0.1.7" }
gloo-net = { version = "0.6.0", default-features = false, features = [
Expand Down
13 changes: 8 additions & 5 deletions nghe-frontend/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use anyhow::Error;
use codee::string::{FromToStringCodec, OptionCodec};
use concat_string::concat_string;
use gloo_net::http;
Expand All @@ -9,6 +8,8 @@ use leptos_use::storage::use_local_storage;
use nghe_api::common::{JsonEndpoint, JsonURL};
use uuid::Uuid;

use crate::{Error, error};

#[derive(Clone)]
pub struct Client {
authorization: String,
Expand Down Expand Up @@ -57,11 +58,13 @@ impl Client {
if response.ok() {
Ok(response.json().await?)
} else {
let code = response.status();
let text = response.text().await?;
if text.is_empty() {
anyhow::bail!("{} {}", response.status(), response.status_text());
}
anyhow::bail!("{text}");
Err(if text.is_empty() {
error::Http { code, text: response.status_text() }.into()
} else {
error::Http { code, text }.into()
})
}
}

Expand Down
8 changes: 2 additions & 6 deletions nghe-frontend/src/components/authentication/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,9 @@ pub fn Login() -> impl IntoView {
let login_action = Action::<_, _, SyncStorage>::new_unsync(move |request: &Request| {
let request = request.clone();
async move {
let api_key = Client::json_no_auth(&request)
.await
.map_err(|error| error.to_string())?
.api_key
.api_key;
let api_key = Client::json_no_auth(&request).await?.api_key.api_key;
set_api_key(Some(api_key));
Ok::<_, String>(())
Ok(())
}
});

Expand Down
4 changes: 2 additions & 2 deletions nghe-frontend/src/components/authentication/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ pub fn Setup() -> impl IntoView {
let setup_action = Action::<_, _, SyncStorage>::new_unsync(|request: &Request| {
let request = request.clone();
async move {
Client::json_no_auth(&request).await.map_err(|error| error.to_string())?;
Ok::<_, String>(())
Client::json_no_auth(&request).await?;
Ok(())
}
});
Effect::new(move || {
Expand Down
87 changes: 42 additions & 45 deletions nghe-frontend/src/components/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,49 @@ use leptos::prelude::*;
use leptos_router::components::{Route, Router, Routes};
use leptos_router::path;

use super::{Error, Home, Loading, Root, authentication};
use super::{Home, Loading, Root, authentication};

pub fn Body() -> impl IntoView {
html::div().class("flex h-dvh box-border").child((
Router(
component_props_builder(&Router)
.base("/frontend")
.children(ToChildren::to_children(move || {
Root(move || {
Routes(
component_props_builder(&Routes)
.fallback(|| "Not found")
.children(ToChildren::to_children(move || {
(
Route(
component_props_builder(&Route)
.path(path!(""))
.view(Home)
.build(),
),
Route(
component_props_builder(&Route)
.path(path!("/loading"))
.view(Loading)
.build(),
),
Route(
component_props_builder(&Route)
.path(path!("/setup"))
.view(authentication::Setup)
.build(),
),
Route(
component_props_builder(&Route)
.path(path!("/login"))
.view(authentication::Login)
.build(),
),
)
}))
.build(),
)
})
}))
.build(),
),
Error(),
html::div().class("flex h-dvh box-border").child(Router(
component_props_builder(&Router)
.base("/frontend")
.children(ToChildren::to_children(move || {
Root(move || {
Routes(
component_props_builder(&Routes)
.fallback(|| "Not found")
.children(ToChildren::to_children(move || {
(
Route(
component_props_builder(&Route)
.path(path!(""))
.view(Home)
.build(),
),
Route(
component_props_builder(&Route)
.path(path!("/loading"))
.view(Loading)
.build(),
),
Route(
component_props_builder(&Route)
.path(path!("/setup"))
.view(authentication::Setup)
.build(),
),
Route(
component_props_builder(&Route)
.path(path!("/login"))
.view(authentication::Login)
.build(),
),
)
}))
.build(),
)
})
}))
.build(),
))
}
50 changes: 0 additions & 50 deletions nghe-frontend/src/components/error/context.rs

This file was deleted.

21 changes: 21 additions & 0 deletions nghe-frontend/src/components/error/generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use leptos::html::ElementChild;
use leptos::prelude::ClassAttribute;
use leptos::{IntoView, html};

pub fn Generic(error: String) -> impl IntoView {
html::section().class("h-full w-full bg-gray-50 dark:bg-gray-900").child(
html::div().class("py-20 px-8 mx-auto max-w-screen-xl lg:py-30 lg:px-12").child(
html::div().class("mx-auto max-w-screen-sm text-center").child((
html::h1()
.class(
"mb-4 text-7xl tracking-tight font-extrabold lg:text-9xl text-red-500 \
dark:text-red-400",
)
.child("Error"),
html::p()
.class("mb-4 text-2xl tracking-tight text-gray-900 md:text-3xl dark:text-white")
.child(error),
)),
),
)
}
23 changes: 23 additions & 0 deletions nghe-frontend/src/components/error/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use leptos::html::ElementChild;
use leptos::prelude::ClassAttribute;
use leptos::{IntoView, html};

use crate::error;

pub fn Http(error: error::Http) -> impl IntoView {
html::section().class("h-full w-full bg-gray-50 dark:bg-gray-900").child(
html::div().class("py-20 px-8 mx-auto max-w-screen-xl lg:py-30 lg:px-12").child(
html::div().class("mx-auto max-w-screen-sm text-center").child((
html::h1()
.class(
"mb-4 text-7xl tracking-tight font-extrabold lg:text-9xl text-red-500 \
dark:text-red-400",
)
.child(error.code),
html::p()
.class("mb-4 text-2xl tracking-tight text-gray-900 md:text-3xl dark:text-white")
.child(error.text),
)),
),
)
}
67 changes: 18 additions & 49 deletions nghe-frontend/src/components/error/mod.rs
Original file line number Diff line number Diff line change
@@ -1,54 +1,23 @@
mod context;
mod toast;
#![allow(clippy::needless_pass_by_value)]

mod generic;
mod http;

use leptos::either::Either;
use leptos::prelude::*;
use leptos::{ev, html};
pub use toast::Toast;

pub fn Error() -> impl IntoView {
let pending_hide = context::PendingHide::signal();
let error = context::Error::signal();
fn Error(errors: ArcRwSignal<Errors>) -> impl IntoView {
let errors = errors();
leptos::logging::error!("{:?}", errors);
errors.into_iter().next().map(|(_, error)| {
let error = error.into_inner();
match error.downcast_ref::<crate::Error>().expect("Could not handle this error type") {
crate::Error::Http(error) => Either::Left(http::Http(error.clone())),
crate::Error::GlooNet(_) => Either::Right(generic::Generic(error.to_string())),
}
})
}

let owner = Owner::current().expect("Owner should be provided");
move || {
let owner = owner.clone();
error().0.map(|error| {
html::div()
.role("alert")
.class(move || {
if pending_hide().0 {
"fixed p-4 text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 \
dark:text-red-400 hover:ring-2 hover:ring-red-400 right-5 bottom-5 \
max-w-full ml-5 md:ml-69 lg:ml-0 lg:max-w-2/4 transition-opacity \
duration-300 ease-out opacity-0"
} else {
"fixed p-4 text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 \
dark:text-red-400 hover:ring-2 hover:ring-red-400 right-5 bottom-5 \
max-w-full ml-5 md:ml-69 lg:ml-0 lg:max-w-2/4 transition-opacity \
duration-300 ease-out"
}
})
.child(
html::div()
.class("flex items-center justify-center")
.child(html::div().class("text-sm font-medium text-justify").child(error)),
)
.on(ev::click, move |_| {
if !pending_hide().0 {
let owner = owner.clone();
owner.with(|| {
context::PendingHide::set();
});
set_timeout(
move || {
owner.with(|| {
context::Error::clear();
});
},
std::time::Duration::from_millis(300),
);
}
})
})
}
pub fn Boundary(child: TypedChildren<impl IntoView + 'static>) -> impl IntoView {
ErrorBoundary(component_props_builder(&ErrorBoundary).fallback(Error).children(child).build())
}
Loading
Loading