From a53ad775dcc7a8279696bed5f663c46e11e4709b Mon Sep 17 00:00:00 2001 From: xeovalyte Date: Thu, 3 Oct 2024 15:59:31 +0200 Subject: [PATCH] Added members management system --- assets/tailwind.css | 9 +++ src/components/layout/auth.rs | 5 +- src/components/layout/icons.rs | 19 ++++++ src/components/settings.rs | 103 ++++++++++++++++++++++++++------- src/util/model/member.rs | 11 ++-- src/util/model/user.rs | 36 +++++++++++- 6 files changed, 151 insertions(+), 32 deletions(-) diff --git a/assets/tailwind.css b/assets/tailwind.css index 1963d74..234a596 100644 --- a/assets/tailwind.css +++ b/assets/tailwind.css @@ -2927,6 +2927,10 @@ details.collapse summary::-webkit-details-marker { margin-left: auto; } +.mr-3 { + margin-right: 0.75rem; +} + .mt-10 { margin-top: 2.5rem; } @@ -3180,6 +3184,11 @@ details.collapse summary::-webkit-details-marker { font-weight: 400; } +.text-error { + --tw-text-opacity: 1; + color: var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity))); +} + .text-primary { --tw-text-opacity: 1; color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity))); diff --git a/src/components/layout/auth.rs b/src/components/layout/auth.rs index 3d84ae2..efe6040 100644 --- a/src/components/layout/auth.rs +++ b/src/components/layout/auth.rs @@ -235,8 +235,5 @@ async fn register( async fn fetch_member(registration_token: String) -> Result { let member = Member::fetch_from_registration_token(registration_token).await?; - match member { - Some(m) => Ok(m), - None => Err(ServerFnError::new("Lid niet gevonden")), - } + Ok(member) } diff --git a/src/components/layout/icons.rs b/src/components/layout/icons.rs index 5cc2101..374f9e0 100644 --- a/src/components/layout/icons.rs +++ b/src/components/layout/icons.rs @@ -121,3 +121,22 @@ pub fn XMark() -> Element { } } } + +#[component] +pub fn Trash() -> Element { + rsx! { + svg { + "fill": "none", + "stroke": "currentColor", + "xmlns": "http://www.w3.org/2000/svg", + "stroke-width": "1.5", + "viewBox": "0 0 24 24", + class: "size-6", + path { + "stroke-linecap": "round", + "stroke-linejoin": "round", + "d": "m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" + } + } + } +} diff --git a/src/components/settings.rs b/src/components/settings.rs index 28b75c5..4948ef9 100644 --- a/src/components/settings.rs +++ b/src/components/settings.rs @@ -1,9 +1,8 @@ use dioxus::prelude::*; +use crate::components::layout::icons; use crate::util::model::{member::Member, session::Session, user::User}; -use super::admin::members; - #[component] pub fn Settings() -> Element { rsx! { @@ -216,9 +215,10 @@ async fn change_password(old_password: String, new_password: String) -> Result<( } fn Members() -> Element { - let members = use_resource(fetch_members); + let mut members = use_resource(fetch_members); let mut is_open = use_signal(|| false); + let mut input_registration_token = use_signal(|| "".to_string()); rsx! { div { @@ -233,25 +233,71 @@ fn Members() -> Element { class: "collapse-content", div { class: "pl-2 flex flex-col gap-3", - div { - class: "flex", - div { - class: "flex flex-col", - div { - class: "", - span { class: "font-bold", "Timo Boomers", } - span { class: "mx-1.5", "•" } - span { "123" } + match &*members.read() { + Some(Ok(res)) => { + let members_state = res.to_owned(); + + rsx! { + for member in members_state { + div { + class: "flex items-center", + div { + class: "flex flex-col", + div { + class: "", + span { class: "font-bold", "{member.name.full}", } + span { class: "mx-1.5", "•" } + span { "{member.id}" } + } + div { + class: "", + span { "LS2" }, + span { class: "mx-1.5", "•" } + span { "A1, B2" } + } + }, + div { + class: "ml-auto mr-3 h-full text-error hover:cursor-pointer", + onclick: move |_| { + let id = member.id.clone(); + async move { + if let Ok(_res) = delete_member_relation(id).await { + members.restart(); + } + } + }, + icons::Trash {} + } + } + div { class: "divider my-0" } + } + div { + class: "w-full join", + input { + r#type: "text", + class: "input input-bordered w-full join-item", + value: "{input_registration_token}", + oninput: move |event| input_registration_token.set(event.value()) + }, + button { + class: "btn join-item btn-neutral", + onclick: move |_| async move { + if let Ok(_res) = relate_member(input_registration_token()).await { + members.restart(); + input_registration_token.set("".to_string()); + } + }, + icons::UserPlus {}, + "Toevoegen", + } + } } - div { - class: "", - span { "LS2" }, - span { class: "mx-1.5", "•" } - span { "A1, B2" } - } - } + }, + Some(Err(_)) => rsx! { + "Error" + }, + None => rsx! { div { "Loading..." } }, } - div { class: "divider my-0" } } }, }, @@ -266,3 +312,20 @@ async fn fetch_members() -> Result, ServerFnError> { Ok(members) } + +#[server] +async fn relate_member(registration_token: String) -> Result<(), ServerFnError> { + let user = Session::fetch_current_user().await?; + let member_id = Member::fetch_from_registration_token(registration_token).await?; + + user.relate_member(member_id.id).await?; + Ok(()) +} + +#[server] +async fn delete_member_relation(member_id: String) -> Result<(), ServerFnError> { + let user = Session::fetch_current_user().await?; + + user.remove_relation(member_id).await?; + Ok(()) +} diff --git a/src/util/model/member.rs b/src/util/model/member.rs index a0e8596..1bdb279 100644 --- a/src/util/model/member.rs +++ b/src/util/model/member.rs @@ -41,15 +41,16 @@ impl Member { pub async fn fetch_from_registration_token( registration_token: String, - ) -> Result, surrealdb::Error> { + ) -> Result { let mut res = DB .query("SELECT id, name.first, name.full, hours, groups, diploma, registration_token FROM ONLY member WHERE registration_token = $registration_token LIMIT 1") .bind(("registration_token", registration_token)) .await?; - let member: Option = res.take(0)?; - - Ok(member) + match res.take(0)? { + Some(m) => Ok(m), + None => Err(crate::Error::NoDocument), + } } pub async fn fetch_from_user(user_id: String) -> Result, crate::Error> { @@ -60,8 +61,6 @@ impl Member { .bind(("user_id", user_id)) .await?; - tracing::info!("{res:?}"); - let members: Vec = res.take(0)?; Ok(members) diff --git a/src/util/model/user.rs b/src/util/model/user.rs index 72c1304..debb998 100644 --- a/src/util/model/user.rs +++ b/src/util/model/user.rs @@ -45,6 +45,40 @@ impl User { } } + pub async fn relate_member(&self, member_id: String) -> Result<(), crate::Error> { + let mut res = DB + .query(" + IF (SELECT * FROM ONLY user_to_member WHERE in = type::thing('user', $user_id) AND out = type::thing('member', $member_id) LIMIT 1) == NONE { + RELATE ONLY (type::thing('user', $user_id))->user_to_member->(type::thing('member', $member_id)); + } ELSE { + THROW 'Relation already exists' + }; + ") + .bind(("user_id", self.id.to_string())) + .bind(("member_id", member_id)) + .await?; + + if res.take_errors().len() != 0 { + return Err(crate::Error::NoDocument); + } + + Ok(()) + } + + pub async fn remove_relation(&self, member_id: String) -> Result<(), crate::Error> { + let mut res = DB + .query("DELETE type::thing('user', $user_id)->user_to_member WHERE out = type::thing('member', $member_id)") + .bind(("user_id", self.id.clone())) + .bind(("member_id", member_id)) + .await?; + + if res.take_errors().len() != 0 { + return Err(crate::Error::NoDocument); + } + + Ok(()) + } + pub async fn verify_credentials( email: String, password: String, @@ -99,8 +133,6 @@ impl User { .bind(("new_password", new_password)) .await?; - tracing::info!("{res:?}"); - if res.take_errors().len() != 0 { return Err(crate::Error::NoDocument); }