Compare commits
2 commits
d6e81a0df0
...
b357f9c525
Author | SHA1 | Date | |
---|---|---|---|
asonix | b357f9c525 | ||
asonix | ea262eb226 |
|
@ -310,7 +310,7 @@ impl CollectionViewSchema for PostsWithTagsByMultipleTags {
|
|||
tags.sort();
|
||||
tags.reverse();
|
||||
|
||||
tags.permute()
|
||||
tags.permute_bounded(6)
|
||||
.map(|tags| {
|
||||
document.header.emit_key_and_value(
|
||||
TagSet {
|
||||
|
|
|
@ -30,6 +30,7 @@ impl FormState {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn CreatePostForm(cx: Scope<CreatePostProps>) -> Element {
|
||||
let form_state = use_ref(cx, || FormState::default());
|
||||
|
||||
|
@ -58,32 +59,37 @@ pub(crate) fn CreatePostForm(cx: Scope<CreatePostProps>) -> Element {
|
|||
cx.render(rsx! {
|
||||
form {
|
||||
onsubmit: submit,
|
||||
div {
|
||||
label {
|
||||
r#for: "title",
|
||||
"Title:",
|
||||
},
|
||||
input {
|
||||
oninput: move |evt| form_state.with_mut(|form| form.title = evt.value.clone()),
|
||||
r#type: "text",
|
||||
name: "title",
|
||||
},
|
||||
class: "post-form centered",
|
||||
h2 {
|
||||
"Create Post",
|
||||
},
|
||||
div {
|
||||
label {
|
||||
r#for: "body",
|
||||
"Post:",
|
||||
class: "form-body",
|
||||
div {
|
||||
label {
|
||||
span { "Title" },
|
||||
input {
|
||||
oninput: move |evt| form_state.with_mut(|form| form.title = evt.value.clone()),
|
||||
r#type: "text",
|
||||
name: "title",
|
||||
},
|
||||
},
|
||||
},
|
||||
input {
|
||||
oninput: move |evt| form_state.with_mut(|form| { form.body = evt.value.clone(); }),
|
||||
r#type: "text",
|
||||
name: "body",
|
||||
div {
|
||||
label {
|
||||
span { "Post" },
|
||||
textarea {
|
||||
oninput: move |evt| form_state.with_mut(|form| { form.body = evt.value.clone(); }),
|
||||
name: "body",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
div {
|
||||
class: "tags",
|
||||
label {
|
||||
r#for: "tags",
|
||||
"Tags:",
|
||||
span { "Tags" },
|
||||
},
|
||||
form_state.with(|form| {
|
||||
form.tags.iter().map(|(index, tag)| {
|
||||
|
@ -92,6 +98,7 @@ pub(crate) fn CreatePostForm(cx: Scope<CreatePostProps>) -> Element {
|
|||
|
||||
rsx! {
|
||||
div {
|
||||
class: "text-with-button",
|
||||
input {
|
||||
oninput: move |evt| form_state.with_mut(|form| {
|
||||
if let Some(tag) = form.tags.get_mut(&index) {
|
||||
|
@ -114,6 +121,7 @@ pub(crate) fn CreatePostForm(cx: Scope<CreatePostProps>) -> Element {
|
|||
}).collect::<Vec<_>>()
|
||||
}).into_iter(),
|
||||
div {
|
||||
class: "text-with-button",
|
||||
input {
|
||||
oninput: move |evt| form_state.with_mut(|form| {
|
||||
form.new_tag = evt.value.clone();
|
||||
|
@ -130,8 +138,9 @@ pub(crate) fn CreatePostForm(cx: Scope<CreatePostProps>) -> Element {
|
|||
value: "Add",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
div {
|
||||
class: "form-footer",
|
||||
input {
|
||||
r#type: "submit",
|
||||
value: "Create Post",
|
||||
|
|
|
@ -37,6 +37,7 @@ impl std::error::Error for HomeError {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn Home<'a>(cx: Scope<'a, HomeProps<'a>>) -> Element<'a> {
|
||||
let client = cx.props.client.clone();
|
||||
|
||||
|
@ -85,20 +86,24 @@ pub(crate) fn Home<'a>(cx: Scope<'a, HomeProps<'a>>) -> Element<'a> {
|
|||
Some(Ok(posts)) => {
|
||||
let rendered = posts.iter().map(|post| {
|
||||
rsx! {
|
||||
div {
|
||||
h3 {
|
||||
article {
|
||||
class: "post centered",
|
||||
h2 {
|
||||
post.title.clone()
|
||||
}
|
||||
p {
|
||||
post.body.clone()
|
||||
}
|
||||
if post.tags.is_empty() {
|
||||
rsx! {"No tags"}
|
||||
} else {
|
||||
rsx! {
|
||||
post.tags.iter().map(|tag| {
|
||||
rsx! { span { tag.clone() } }
|
||||
})
|
||||
div {
|
||||
class: "tags",
|
||||
if post.tags.is_empty() {
|
||||
rsx! {"No tags"}
|
||||
} else {
|
||||
rsx! {
|
||||
post.tags.iter().map(|tag| {
|
||||
rsx! { span { "#", tag.clone() } }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,21 +2,48 @@ use api_types::{Auth, AuthResponse};
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_router::use_router;
|
||||
use reqwest::Client;
|
||||
use tracing::Instrument;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum LoginError {
|
||||
Request(reqwest::Error),
|
||||
InvalidForm,
|
||||
MissingUsername,
|
||||
MissingPassword,
|
||||
LoginFailed,
|
||||
}
|
||||
|
||||
impl LoginError {
|
||||
fn username_errors(&self) -> Option<String> {
|
||||
if matches!(self, Self::MissingUsername) {
|
||||
Some(self.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn password_errors(&self) -> Option<String> {
|
||||
if matches!(self, Self::MissingPassword) {
|
||||
Some(self.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn form_errors(&self) -> Option<String> {
|
||||
if matches!(self, Self::Request(_) | Self::LoginFailed) {
|
||||
Some(self.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LoginError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Request(_) => write!(f, "Failed to request upstream"),
|
||||
Self::InvalidForm => write!(f, "Login form is invalid"),
|
||||
Self::LoginFailed => write!(f, "Login failed"),
|
||||
Self::Request(_) => write!(f, "Failed to log in, try again or contact support"),
|
||||
Self::MissingUsername => write!(f, "Username is required"),
|
||||
Self::MissingPassword => write!(f, "Password is required"),
|
||||
Self::LoginFailed => write!(f, "Username or password is incorrect"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,86 +69,132 @@ pub(crate) struct LoginProps<'a> {
|
|||
pub(crate) client: &'a Client,
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn on_submit(
|
||||
client: Client,
|
||||
username: Option<String>,
|
||||
password: Option<String>,
|
||||
) -> Result<String, LoginError> {
|
||||
let username = username.ok_or(LoginError::MissingUsername)?;
|
||||
let password = password.ok_or(LoginError::MissingPassword)?;
|
||||
|
||||
if username.is_empty() {
|
||||
return Err(LoginError::MissingUsername);
|
||||
}
|
||||
if password.is_empty() {
|
||||
return Err(LoginError::MissingPassword);
|
||||
}
|
||||
|
||||
let auth = Auth { username, password };
|
||||
|
||||
tracing::info!("Authenticating user");
|
||||
let response = client
|
||||
.post("http://localhost:8006/sessions")
|
||||
.json(&auth)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if response.status().is_success() {
|
||||
let json = response.json::<AuthResponse>().await?;
|
||||
return Ok(json.token);
|
||||
}
|
||||
|
||||
Err(LoginError::LoginFailed)
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn LoginForm<'a>(cx: Scope<'a, LoginProps<'a>>) -> Element<'a> {
|
||||
let auth = use_state(cx, || Auth {
|
||||
username: String::new(),
|
||||
password: String::new(),
|
||||
});
|
||||
|
||||
let router = use_router(cx).clone();
|
||||
|
||||
let client: Client = cx.props.client.clone();
|
||||
let error: &UseState<Option<LoginError>> = use_state(cx, || None);
|
||||
let router = use_router(cx);
|
||||
let client = cx.props.client;
|
||||
let session_token = cx.props.session_token;
|
||||
|
||||
let _future = use_future(cx, (auth, session_token), move |(auth, session_token)| {
|
||||
async move {
|
||||
if session_token.get().is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let auth = auth.get();
|
||||
|
||||
if auth.username.is_empty() || auth.password.is_empty() {
|
||||
return Err(LoginError::InvalidForm);
|
||||
}
|
||||
|
||||
tracing::info!("Authenticating user");
|
||||
let response = client
|
||||
.post("http://localhost:8006/sessions")
|
||||
.json(auth)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if response.status().is_success() {
|
||||
let json = response.json::<AuthResponse>().await?;
|
||||
session_token.set(Some(json.token));
|
||||
router.push_route("/home", None, None);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(LoginError::LoginFailed)
|
||||
}
|
||||
.instrument(tracing::info_span!("Running login future"))
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
form {
|
||||
class: "login-form centered",
|
||||
onsubmit: move |event| {
|
||||
let Some(username) = event.data.values.get("username") else {
|
||||
return;
|
||||
};
|
||||
let Some(password) = event.data.values.get("password") else {
|
||||
return;
|
||||
};
|
||||
let username = event.data.values.get("username").cloned();
|
||||
let password = event.data.values.get("password").cloned();
|
||||
|
||||
auth.set(Auth {
|
||||
username: username.to_string(),
|
||||
password: password.to_string(),
|
||||
});
|
||||
let error = error.clone();
|
||||
let router = router.clone();
|
||||
let client = client.clone();
|
||||
let session_token = session_token.clone();
|
||||
|
||||
cx.spawn(async move {
|
||||
match on_submit(client, username, password).await {
|
||||
Ok(token) => {
|
||||
error.set(None);
|
||||
session_token.set(Some(token));
|
||||
router.push_route("/home", None, None);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Setting error: {}", e);
|
||||
error.set(Some(e));
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
div {
|
||||
label {
|
||||
r#for: "username",
|
||||
"Username"
|
||||
},
|
||||
input {
|
||||
r#type: "text",
|
||||
name: "username",
|
||||
},
|
||||
h2 {
|
||||
"Login",
|
||||
},
|
||||
div {
|
||||
label {
|
||||
r#for: "password",
|
||||
"Password"
|
||||
},
|
||||
input {
|
||||
r#type: "password",
|
||||
name: "password",
|
||||
},
|
||||
if let Some(errors) = error.get().as_ref().and_then(|e| e.form_errors()) {
|
||||
Some(rsx! {
|
||||
div {
|
||||
class: "errors",
|
||||
errors,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
input {
|
||||
r#type: "submit",
|
||||
"Login"
|
||||
div {
|
||||
class: "form-body",
|
||||
div {
|
||||
label {
|
||||
span { "Username" },
|
||||
input {
|
||||
r#type: "text",
|
||||
name: "username",
|
||||
},
|
||||
if let Some(errors) = error.get().as_ref().and_then(|e| e.username_errors()) {
|
||||
Some(rsx! {
|
||||
div {
|
||||
class: "errors",
|
||||
errors,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
},
|
||||
div {
|
||||
label {
|
||||
span { "Password" },
|
||||
input {
|
||||
r#type: "password",
|
||||
name: "password",
|
||||
},
|
||||
if let Some(errors) = error.get().as_ref().and_then(|e| e.password_errors()) {
|
||||
Some(rsx! {
|
||||
div {
|
||||
class: "errors",
|
||||
errors,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
div {
|
||||
class: "form-footer",
|
||||
input {
|
||||
r#type: "submit",
|
||||
value: "Login"
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use std::{collections::BTreeMap, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use api_types::Post;
|
||||
use axum::{extract::WebSocketUpgrade, response::Html, routing::get, Extension, Router};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_router::{Link, Redirect, Route, Router};
|
||||
|
@ -63,9 +62,12 @@ fn application(cx: Scope<Arc<ApplicationState>>) -> Element {
|
|||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
rsx! {
|
||||
Route { to: "/login", LoginForm { session_token: session_token, client: client } },
|
||||
Route { to: "/register", RegistrationForm { session_token: session_token, client: client } },
|
||||
}
|
||||
}
|
||||
Route { to: "/login", LoginForm { session_token: session_token, client: client } },
|
||||
Route { to: "/register", RegistrationForm { session_token: session_token, client: client } },
|
||||
Redirect { from: "", to: "/posts" },
|
||||
}
|
||||
})
|
||||
|
@ -119,7 +121,160 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head> <title>Dioxus LiveView with Axum</title> </head>
|
||||
<head>
|
||||
<title>Dioxus LiveView with Axum</title>
|
||||
<style>
|
||||
* {{
|
||||
box-sizing: border-box;
|
||||
}}
|
||||
body {{
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
background-color: #333;
|
||||
color: #f5f5f5;
|
||||
font-family: sans;
|
||||
}}
|
||||
.errors {{
|
||||
color: #ee4949;
|
||||
font-weight: 500;
|
||||
}}
|
||||
nav {{
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
display: flex;
|
||||
margin-bottom: 32px;
|
||||
}}
|
||||
nav > div {{
|
||||
padding: 16px;
|
||||
}}
|
||||
.centered {{
|
||||
margin: 32px auto;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
}}
|
||||
.centered > h2,
|
||||
.centered > p {{
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}}
|
||||
.centered > h2,
|
||||
.centered > p,
|
||||
.centered > div {{
|
||||
margin: 0;
|
||||
padding: 16px 32px;
|
||||
}}
|
||||
.centered .form-footer {{
|
||||
border-top: 1px solid #e5e5e5;
|
||||
}}
|
||||
form > .errors {{
|
||||
padding: 16px 32px 0;
|
||||
}}
|
||||
.form-body > div {{
|
||||
padding: 8px 0;
|
||||
}}
|
||||
label {{
|
||||
display: block;
|
||||
}}
|
||||
label > .errors {{
|
||||
padding-top: 4px;
|
||||
}}
|
||||
label > span {{
|
||||
display: block;
|
||||
padding-bottom: 4px;
|
||||
font-weight: 500;
|
||||
}}
|
||||
label > textarea {{
|
||||
min-height: 350px;
|
||||
}}
|
||||
.text-with-button {{
|
||||
display: inline-flex;
|
||||
padding: 16px;
|
||||
width: 433px;
|
||||
}}
|
||||
.text-with-button input[type=text],
|
||||
label > textarea,
|
||||
label > input[type=text],
|
||||
label > input[type=password] {{
|
||||
display: block;
|
||||
outline: none;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 2px;
|
||||
padding: 8px 16px;
|
||||
line-height: 22px;
|
||||
font-size: 16px;
|
||||
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1);
|
||||
width: 100%;
|
||||
}}
|
||||
.text-with-button input[type=text]:hover,
|
||||
label > textarea:hover,
|
||||
label > input[type=text]:hover,
|
||||
label > input[type=password]:hover {{
|
||||
border-color: #ff84ca;
|
||||
}}
|
||||
.text-with-button input[type=text]:focus,
|
||||
label > textarea:focus,
|
||||
label > input[type=text]:focus,
|
||||
label > input[type=password]:focus {{
|
||||
border-color: #ff84ca;
|
||||
box-shadow: inset 0 0 6px rgba(255, 132, 202, 0.4), 0 0 4px rgba(255, 132, 202, 0.2);
|
||||
}}
|
||||
.text-with-button input[type=button],
|
||||
.form-footer input[type=submit] {{
|
||||
border: 1px solid #ff84ca;
|
||||
border-radius: 2px;
|
||||
background-color: #ffc2e5;
|
||||
padding: 8px 16px;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
}}
|
||||
.text-with-button input[type=button]:hover,
|
||||
.form-footer input[type=submit]:hover {{
|
||||
background-color: #ffb2de;
|
||||
}}
|
||||
.text-with-button input[type=text] {{
|
||||
border-radius: 2px 0 0 2px;
|
||||
}}
|
||||
.text-with-button input[type=button] {{
|
||||
border-radius: 0 2px 2px 0;
|
||||
border-left: none;
|
||||
background-color: #f5f5f5;
|
||||
border-color: #e5e5e5;
|
||||
}}
|
||||
.text-with-button input[type=button]:hover {{
|
||||
background-color: #efefef;
|
||||
}}
|
||||
.login-form,
|
||||
.registration-form {{
|
||||
max-width: 400px;
|
||||
}}
|
||||
.post,
|
||||
.post-form {{
|
||||
max-width: 900px;
|
||||
}}
|
||||
.centered.post-form > div.tags {{
|
||||
padding: 16px;
|
||||
}}
|
||||
.centered.post-form > div.tags > label {{
|
||||
padding: 0 16px;
|
||||
}}
|
||||
.post .tags {{
|
||||
padding: 16px;
|
||||
}}
|
||||
.post .tags > span {{
|
||||
display: inline-block;
|
||||
margin: 0 8px;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #ff84ca;
|
||||
background-color: #ffc2e5;
|
||||
color: #000;
|
||||
border-radius: 2px;
|
||||
font-weight: 500;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body> <div id="main"></div> </body>
|
||||
{glue}
|
||||
</html>
|
||||
|
|
|
@ -2,12 +2,13 @@ use api_types::{AuthResponse, NewUser};
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_router::use_router;
|
||||
use reqwest::Client;
|
||||
use tracing::Instrument;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum RegistrationError {
|
||||
Request(reqwest::Error),
|
||||
InvalidForm,
|
||||
MissingUsername,
|
||||
MissingPassword,
|
||||
PasswordMismatch,
|
||||
RegistrationFailed,
|
||||
LoginFailed,
|
||||
}
|
||||
|
@ -16,7 +17,9 @@ impl std::fmt::Display for RegistrationError {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Request(_) => write!(f, "Failed to request upstream"),
|
||||
Self::InvalidForm => write!(f, "Registration form is invalid"),
|
||||
Self::MissingUsername => write!(f, "Username is required"),
|
||||
Self::MissingPassword => write!(f, "Password is required"),
|
||||
Self::PasswordMismatch => write!(f, "Passwords do not match"),
|
||||
Self::RegistrationFailed => write!(f, "Invalid credentials"),
|
||||
Self::LoginFailed => write!(f, "Login failed"),
|
||||
}
|
||||
|
@ -44,101 +47,128 @@ pub(crate) struct RegistrationProps<'a> {
|
|||
pub(crate) client: &'a Client,
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn on_submit(
|
||||
client: Client,
|
||||
username: Option<String>,
|
||||
password: Option<String>,
|
||||
password_confirmation: Option<String>,
|
||||
) -> Result<String, RegistrationError> {
|
||||
let username = username.ok_or(RegistrationError::MissingUsername)?.clone();
|
||||
let password = password.ok_or(RegistrationError::MissingPassword)?.clone();
|
||||
let password_confirmation = password_confirmation
|
||||
.ok_or(RegistrationError::MissingPassword)?
|
||||
.clone();
|
||||
|
||||
if username.is_empty() {
|
||||
return Err(RegistrationError::MissingUsername);
|
||||
}
|
||||
|
||||
if password.is_empty() {
|
||||
return Err(RegistrationError::MissingPassword);
|
||||
}
|
||||
|
||||
if password != password_confirmation {
|
||||
return Err(RegistrationError::PasswordMismatch);
|
||||
}
|
||||
|
||||
let new_user = NewUser { username, password };
|
||||
|
||||
tracing::info!("Creating user");
|
||||
let response = client
|
||||
.post("http://localhost:8006/users")
|
||||
.json(&new_user)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(RegistrationError::RegistrationFailed);
|
||||
}
|
||||
|
||||
tracing::info!("Authenticating user");
|
||||
let response = client
|
||||
.post("http://localhost:8006/sessions")
|
||||
.json(&new_user)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if response.status().is_success() {
|
||||
let json = response.json::<AuthResponse>().await?;
|
||||
return Ok(json.token);
|
||||
}
|
||||
|
||||
Err(RegistrationError::LoginFailed)
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn RegistrationForm<'a>(cx: Scope<'a, RegistrationProps<'a>>) -> Element<'a> {
|
||||
let new_user = use_state(cx, || NewUser {
|
||||
username: String::new(),
|
||||
password: String::new(),
|
||||
});
|
||||
|
||||
let router = use_router(cx).clone();
|
||||
|
||||
let client: Client = cx.props.client.clone();
|
||||
let router = use_router(cx);
|
||||
let client: &Client = cx.props.client;
|
||||
let session_token = cx.props.session_token;
|
||||
|
||||
let _future = use_future(
|
||||
cx,
|
||||
(new_user, session_token),
|
||||
move |(new_user, session_token)| {
|
||||
async move {
|
||||
if session_token.get().is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let new_user = new_user.get();
|
||||
|
||||
if new_user.username.is_empty() || new_user.password.is_empty() {
|
||||
return Err(RegistrationError::InvalidForm);
|
||||
}
|
||||
|
||||
tracing::info!("Creating user");
|
||||
let response = client
|
||||
.post("http://localhost:8006/users")
|
||||
.json(new_user)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(RegistrationError::RegistrationFailed);
|
||||
}
|
||||
|
||||
tracing::info!("Authenticating user");
|
||||
let response = client
|
||||
.post("http://localhost:8006/sessions")
|
||||
.json(new_user)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if response.status().is_success() {
|
||||
let json = response.json::<AuthResponse>().await?;
|
||||
session_token.set(Some(json.token));
|
||||
router.push_route("/home", None, None);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(RegistrationError::LoginFailed)
|
||||
}
|
||||
.instrument(tracing::info_span!("Running create user future"))
|
||||
},
|
||||
);
|
||||
|
||||
cx.render(rsx! {
|
||||
form {
|
||||
class: "registration-form centered",
|
||||
onsubmit: move |event| {
|
||||
let Some(username) = event.data.values.get("username") else {
|
||||
return;
|
||||
};
|
||||
let Some(password) = event.data.values.get("password") else {
|
||||
return;
|
||||
};
|
||||
let username = event.data.values.get("username").cloned();
|
||||
let password = event.data.values.get("password").cloned();
|
||||
let password_confirmation = event.data.values.get("password-confirmation").cloned();
|
||||
|
||||
new_user.set(NewUser {
|
||||
username: username.to_string(),
|
||||
password: password.to_string(),
|
||||
let session_token = session_token.clone();
|
||||
let client = client.clone();
|
||||
let router = router.clone();
|
||||
|
||||
cx.spawn(async move {
|
||||
match on_submit(client, username, password, password_confirmation).await {
|
||||
Ok(token) => {
|
||||
session_token.set(Some(token));
|
||||
router.push_route("/home", None, None);
|
||||
}
|
||||
Err(_) => {
|
||||
// TODO: Handle this error somehow
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
h2 {
|
||||
"Create Account",
|
||||
},
|
||||
div {
|
||||
label {
|
||||
r#for: "username",
|
||||
"Username"
|
||||
class: "form-body",
|
||||
div {
|
||||
label {
|
||||
span { "Username" },
|
||||
input {
|
||||
r#type: "text",
|
||||
name: "username",
|
||||
},
|
||||
},
|
||||
},
|
||||
input {
|
||||
r#type: "text",
|
||||
name: "username",
|
||||
div {
|
||||
label {
|
||||
span { "Password" },
|
||||
input {
|
||||
r#type: "password",
|
||||
name: "password",
|
||||
},
|
||||
},
|
||||
}
|
||||
div {
|
||||
label {
|
||||
span { "Confirm Password" },
|
||||
input {
|
||||
r#type: "password",
|
||||
name: "password-confirmation",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
div {
|
||||
label {
|
||||
r#for: "password",
|
||||
"Password"
|
||||
},
|
||||
class: "form-footer",
|
||||
input {
|
||||
r#type: "password",
|
||||
name: "password",
|
||||
r#type: "submit",
|
||||
value: "Create Account"
|
||||
},
|
||||
}
|
||||
input {
|
||||
r#type: "submit",
|
||||
"Create Account"
|
||||
},
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue