feat: add base app
This commit is contained in:
37
crates/app/Cargo.toml
Normal file
37
crates/app/Cargo.toml
Normal file
@@ -0,0 +1,37 @@
|
||||
[package]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
leptos.workspace = true
|
||||
leptos_meta.workspace = true
|
||||
leptos_router.workspace = true
|
||||
server_fn.workspace = true
|
||||
axum = { workspace = true, optional = true, features = ["macros"] }
|
||||
leptos_axum = { workspace = true, optional = true }
|
||||
|
||||
#tokio = { workspace = true, optional = true }
|
||||
serde.workspace = true
|
||||
http.workspace = true
|
||||
cfg-if.workspace = true
|
||||
thiserror.workspace = true
|
||||
reqwest = { version = "0.12.11", optional = true, features = ["json"] }
|
||||
serde_json = { version = "1.0.134", optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:leptos_axum",
|
||||
"dep:serde_json",
|
||||
"dep:axum",
|
||||
]
|
||||
reqwest = ["dep:reqwest"]
|
||||
axum = ["dep:axum"]
|
||||
serde_json = ["dep:serde_json"]
|
74
crates/app/src/error_template.rs
Normal file
74
crates/app/src/error_template.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use cfg_if::cfg_if;
|
||||
use http::status::StatusCode;
|
||||
use leptos::prelude::*;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
use leptos_axum::ResponseOptions;
|
||||
|
||||
#[derive(Clone, Debug, Error)]
|
||||
pub enum AppError {
|
||||
#[error("Not Found")]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl AppError {
|
||||
pub fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
AppError::NotFound => StatusCode::NOT_FOUND,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A basic function to display errors served by the error boundaries.
|
||||
// Feel free to do more complicated things here than just displaying the error.
|
||||
#[component]
|
||||
pub fn ErrorTemplate(
|
||||
#[prop(optional)] outside_errors: Option<Errors>,
|
||||
#[prop(optional)] errors: Option<RwSignal<Errors>>,
|
||||
) -> impl IntoView {
|
||||
let errors = match outside_errors {
|
||||
Some(e) => RwSignal::new(e),
|
||||
None => match errors {
|
||||
Some(e) => e,
|
||||
None => panic!("No Errors found and we expected errors!"),
|
||||
},
|
||||
};
|
||||
// Get Errors from Signal
|
||||
let errors = errors.get_untracked();
|
||||
|
||||
// Downcast lets us take a type that implements `std::error::Error`
|
||||
let errors: Vec<AppError> = errors
|
||||
.into_iter()
|
||||
.filter_map(|(_k, v)| v.downcast_ref::<AppError>().cloned())
|
||||
.collect();
|
||||
println!("Errors: {errors:#?}");
|
||||
|
||||
// Only the response code for the first error is actually sent from the server
|
||||
// this may be customized by the specific application
|
||||
cfg_if! { if #[cfg(feature="ssr")] {
|
||||
let response = use_context::<ResponseOptions>();
|
||||
if let Some(response) = response {
|
||||
response.set_status(errors[0].status_code());
|
||||
}
|
||||
}}
|
||||
|
||||
view! {
|
||||
<h1>{if errors.len() > 1 { "Errors" } else { "Error" }}</h1>
|
||||
<For
|
||||
// a function that returns the items we're iterating over; a signal is fine
|
||||
each=move || { errors.clone().into_iter().enumerate() }
|
||||
// a unique key for each item as a reference
|
||||
key=|(index, _error)| *index
|
||||
// renders each item to a view
|
||||
children=move |error| {
|
||||
let error_string = error.1.to_string();
|
||||
let error_code = error.1.status_code();
|
||||
view! {
|
||||
<h2>{error_code.to_string()}</h2>
|
||||
<p>"Error: " {error_string}</p>
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
61
crates/app/src/lib.rs
Normal file
61
crates/app/src/lib.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use crate::error_template::{AppError, ErrorTemplate};
|
||||
|
||||
use leptos::prelude::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::{components::*, StaticSegment};
|
||||
|
||||
pub mod error_template;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod state;
|
||||
|
||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
view! {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<AutoReload options=options.clone() />
|
||||
<HydrationScripts options />
|
||||
<MetaTags />
|
||||
</head>
|
||||
<body>
|
||||
<App />
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
// Provides context that manages stylesheets, titles, meta tags, etc.
|
||||
provide_meta_context();
|
||||
|
||||
view! {
|
||||
<Stylesheet id="leptos" href="/pkg/client.css" />
|
||||
|
||||
// sets the document title
|
||||
<Title text="client" />
|
||||
|
||||
// content for this welcome page
|
||||
<Router>
|
||||
<main class="">
|
||||
<Routes fallback=|| {
|
||||
let mut outside_errors = Errors::default();
|
||||
outside_errors.insert_with_default_key(AppError::NotFound);
|
||||
view! { <ErrorTemplate outside_errors /> }.into_view()
|
||||
}>
|
||||
<Route path=StaticSegment("") view=HomePage />
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn HomePage() -> impl IntoView {
|
||||
view! {
|
||||
<h1> "client" </h1>
|
||||
}
|
||||
}
|
14
crates/app/src/state.rs
Normal file
14
crates/app/src/state.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use axum::extract::FromRef;
|
||||
use leptos::prelude::expect_context;
|
||||
use server_fn::ServerFnError;
|
||||
|
||||
#[derive(FromRef, Clone)]
|
||||
pub struct State {}
|
||||
|
||||
pub async fn get_state() -> Result<State, ServerFnError> {
|
||||
let state = expect_context::<crate::state::State>();
|
||||
let axum::extract::State(state): axum::extract::State<crate::state::State> =
|
||||
leptos_axum::extract_with_state(&state).await?;
|
||||
|
||||
Ok(state)
|
||||
}
|
Reference in New Issue
Block a user