Compare commits

..

No commits in common. "a75df84dcb55d4368a5491d16cf28c04a436f2ae" and "ec195dfbf7f135c7cf2e83834d92ad9f814f3047" have entirely different histories.

10 changed files with 1525 additions and 1278 deletions

2363
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,40 +7,28 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
dioxus = { version = "0.6", features = ["fullstack", "router"] } dioxus = { version = "0.5", features = ["fullstack", "router"] }
dioxus-cli-config = { version = "0.6", optional = true } web-sys = { version = "0.3.70", features = ["Window", "Location"] }
web-sys = { version = "0.3", features = ["Window", "Location"] }
tokio = { version = "1.42", features = ["macros", "rt-multi-thread"], optional = true } tokio = { version = "1.38", features = ["macros", "rt-multi-thread"], optional = true }
axum = { version = "0.7", optional = true } axum = { version = "0.7", optional = true }
axum-extra = { version = "0.9", features = ["cookie"], optional = true } axum-extra = { version = "0.9", features = ["cookie"], optional = true }
time = { version = "0.3", optional = true } time = { version = "0.3", optional = true }
once_cell = { version = "1.20", optional = true } once_cell = { version = "1.19", optional = true }
surrealdb = { version = "2.1", optional = true } surrealdb = { version = "2.0", optional = true }
thiserror = { version = "2.0" } thiserror = { version = "1.0" }
strum = { version = "0.26", features = ["derive"] } strum = { version = "0.26", features = ["derive"] }
csv = { version = "1.3", optional = true } csv = { version = "1.3", optional = true }
# Debug # Debug
tracing = "0.1" tracing = "0.1"
manganis = "0.6" dioxus-logger = "0.5"
manganis = "0.2"
[features] [features]
default = [] default = []
server = [ "dioxus/server", "dioxus-cli-config", "tokio", "axum", "axum-extra", "time", "once_cell", "surrealdb", "csv" ] server = [ "dioxus/axum", "tokio", "axum", "axum-extra", "time", "once_cell", "surrealdb", "csv" ]
web = ["dioxus/web"] web = ["dioxus/web"]
[profile]
[profile.wasm-dev]
inherits = "dev"
opt-level = 1
[profile.server-dev]
inherits = "dev"
[profile.android-dev]
inherits = "dev"

View File

@ -3352,10 +3352,6 @@ details.collapse summary::-webkit-details-marker {
display: contents; display: contents;
} }
.hidden {
display: none;
}
.size-6 { .size-6 {
width: 1.5rem; width: 1.5rem;
height: 1.5rem; height: 1.5rem;
@ -3396,12 +3392,12 @@ details.collapse summary::-webkit-details-marker {
min-height: 4rem; min-height: 4rem;
} }
.w-1\/3 { .w-1\/2 {
width: 33.333333%; width: 50%;
} }
.w-2\/3 { .w-1\/4 {
width: 66.666667%; width: 25%;
} }
.w-80 { .w-80 {

View File

@ -2,7 +2,8 @@ use crate::util::model::{
member::{Member, MembersMigration}, member::{Member, MembersMigration},
session::Session, session::Session,
}; };
use dioxus::prelude::*; use dioxus::prelude::{dioxus_elements::FileEngine, *};
use std::sync::Arc;
#[derive(Debug)] #[derive(Debug)]
struct UploadedFile { struct UploadedFile {
@ -30,12 +31,12 @@ pub fn Migration() -> Element {
li { class: "step step-primary", "Uploaden" }, li { class: "step step-primary", "Uploaden" },
li { li {
class: "step", class: "step",
class: if let Steps::Verify | Steps::Done = *step.read() { "step-primary" }, class: if let Steps::Verify | Steps::Done = *step.read() { { "step-primary" } },
"Controleren" "Controleren"
}, },
li { li {
class: "step", class: "step",
class: if let Steps::Done = *step.read() { "step-primary" }, class: if let Steps::Done = *step.read() { { "step-primary" } },
"Klaar" "Klaar"
}, },
}, },
@ -50,23 +51,26 @@ pub fn Migration() -> Element {
#[component] #[component]
fn Upload(step: Signal<Steps>, members_migration: Signal<(u16, MembersMigration)>) -> Element { fn Upload(step: Signal<Steps>, members_migration: Signal<(u16, MembersMigration)>) -> Element {
let mut file_uploaded: Signal<Option<UploadedFile>> = use_signal(|| None); let mut file_uploaded = use_signal(|| None);
let mut loading = use_signal(|| false); let mut loading = use_signal(|| false);
let read_files = move |file_engine: Arc<dyn FileEngine>| async move {
let files = file_engine.files();
for file_name in &files {
if let Some(contents) = file_engine.read_file_to_string(file_name).await {
file_uploaded.set(Some(UploadedFile {
name: file_name.clone(),
contents,
}));
}
}
};
let upload_files = move |evt: FormEvent| async move { let upload_files = move |evt: FormEvent| async move {
// TODO: Uncomment after bugfix dioxus if let Some(file_engine) = evt.files() {
// if let Some(file_engine) = &evt.files() { read_files(file_engine).await;
// let files = file_engine.files(); }
// for file_name in &files {
// if let Some(file) = file_engine.read_file_to_string(file_name).await {
// file_uploaded.set(Some(UploadedFile {
// name: file_name.to_owned(),
// contents: file,
// }))
// }
// }
// }
}; };
let sumbit = move |_evt: FormEvent| async move { let sumbit = move |_evt: FormEvent| async move {

View File

@ -1,4 +1,3 @@
use crate::util::model::news::{Target, TargetKind};
use std::collections::HashMap; use std::collections::HashMap;
use dioxus::prelude::*; use dioxus::prelude::*;
@ -50,36 +49,27 @@ pub fn NewsCreate() -> Element {
li { class: "step step-primary", "Inhoud" }, li { class: "step step-primary", "Inhoud" },
li { li {
class: "step", class: "step",
class: if let Steps::Targets | Steps::Done | Steps::Verify = *step.read() { "step-primary" }, class: if let Steps::Targets | Steps::Done | Steps::Verify = *step.read() { { "step-primary" } },
"Naar" "Naar"
} }
li { li {
class: "step", class: "step",
class: if let Steps::Verify | Steps::Done = *step.read() { "step-primary" }, class: if let Steps::Verify | Steps::Done = *step.read() { { "step-primary" } },
"Controleren" "Controleren"
} }
li { li {
class: "step", class: "step",
class: if let Steps::Done = *step.read() { "step-primary" }, class: if let Steps::Done = *step.read() { { "step-primary" } },
"Klaar" "Klaar"
} }
} }
div { div {
class: "flex flex-col gap-y-5", class: "flex flex-col gap-y-5",
div { match *step.read() {
class: if let Steps::Message = *step.read() { "" } else { "hidden" }, Steps::Message => rsx! { Message { step: step, title: form.title, body: form.body } },
Message { Steps::Targets => rsx! { TargetSelect { step: step, targets: form.targets } },
step: step, Steps::Verify => rsx! { { } },
title: form.title, Steps::Done => rsx! { { } },
body: form.body,
}
}
div {
class: if let Steps::Targets = *step.read() { "" } else { "hidden" },
TargetSelect {
step: step,
targets: form.targets,
}
} }
} }
} }
@ -91,6 +81,9 @@ fn Message(step: Signal<Steps>, title: Signal<String>, body: Signal<String>) ->
let submit = move |event: FormEvent| { let submit = move |event: FormEvent| {
title.set(event.values()["title"].as_value()); title.set(event.values()["title"].as_value());
body.set(event.values()["body"].as_value()); body.set(event.values()["body"].as_value());
tracing::info!("{title}");
step.set(Steps::Targets); step.set(Steps::Targets);
}; };
@ -108,6 +101,7 @@ fn Message(step: Signal<Steps>, title: Signal<String>, body: Signal<String>) ->
r#type: "text", r#type: "text",
required: true, required: true,
name: "title", name: "title",
value: "{title}",
class: "input input-bordered w-full", class: "input input-bordered w-full",
} }
} }
@ -119,6 +113,7 @@ fn Message(step: Signal<Steps>, title: Signal<String>, body: Signal<String>) ->
}, },
textarea { textarea {
name: "body", name: "body",
value: "{body}",
class: "textarea textarea-bordered w-full", class: "textarea textarea-bordered w-full",
required: true, required: true,
} }
@ -135,6 +130,39 @@ fn Message(step: Signal<Steps>, title: Signal<String>, body: Signal<String>) ->
} }
} }
#[derive(PartialEq, Clone, Copy, Debug)]
enum TargetKind {
None,
All,
Group,
Hourgroup,
Hour,
Member,
Account,
Day,
}
#[derive(PartialEq, Debug)]
struct Target {
kind: TargetKind,
value: String,
}
impl TargetKind {
fn from_string(input: &str) -> Self {
match input {
"all" => Self::All,
"group" => Self::Group,
"hourgroup" => Self::Hourgroup,
"hour" => Self::Hour,
"member" => Self::Member,
"account" => Self::Account,
"day" => Self::Day,
_ => Self::None,
}
}
}
#[component] #[component]
fn TargetSelect(step: Signal<Steps>, targets: Signal<HashMap<u32, Target>>) -> Element { fn TargetSelect(step: Signal<Steps>, targets: Signal<HashMap<u32, Target>>) -> Element {
let mut target_id = use_signal(|| 1); let mut target_id = use_signal(|| 1);
@ -144,17 +172,12 @@ fn TargetSelect(step: Signal<Steps>, targets: Signal<HashMap<u32, Target>>) -> E
filtered_targets.sort_unstable(); filtered_targets.sort_unstable();
tracing::info!("targets updated");
filtered_targets filtered_targets
}); });
let submit = move |_| {
step.set(Steps::Verify);
};
rsx! { rsx! {
form {
class: "w-full",
onsubmit: submit,
label { label {
class: "form-control w-full", class: "form-control w-full",
div { div {
@ -184,7 +207,6 @@ fn TargetSelect(step: Signal<Steps>, targets: Signal<HashMap<u32, Target>>) -> E
class: "w-full flex gap-x-3 justify-end", class: "w-full flex gap-x-3 justify-end",
button { button {
class: "btn", class: "btn",
r#type: "button",
onclick: move |_| { onclick: move |_| {
step.set(Steps::Message) step.set(Steps::Message)
}, },
@ -192,29 +214,32 @@ fn TargetSelect(step: Signal<Steps>, targets: Signal<HashMap<u32, Target>>) -> E
} }
button { button {
class: "btn btn-primary", class: "btn btn-primary",
onclick: move |_| {
step.set(Steps::Verify)
},
"Volgende", "Volgende",
} }
} }
} }
} }
}
#[component] #[component]
fn TargetEntry(mut targets: Signal<HashMap<u32, Target>>, id: u32) -> Element { fn TargetEntry(mut targets: Signal<HashMap<u32, Target>>, id: u32) -> Element {
let kind = use_memo(move || targets.read().get(&id).unwrap().kind); let kind = use_memo(move || targets.read().get(&id).unwrap().kind);
tracing::info!("Comonent rendered!");
rsx! { rsx! {
div { div {
class: "join w-full mt-3", class: "join w-full mt-3",
select { select {
class: "select select-bordered join-item w-1/3", class: "select select-bordered join-item w-1/2",
required: true,
oninput: move |event| { oninput: move |event| {
let target_kind = TargetKind::from_string(&event.value()); let target_kind = TargetKind::from_string(&event.value());
targets.write().get_mut(&id).unwrap().kind = target_kind; targets.write().get_mut(&id).unwrap().kind = target_kind;
targets.write().get_mut(&id).unwrap().value = String::from(""); targets.write().get_mut(&id).unwrap().value = String::new();
}, },
option { disabled: true, selected: true, value: "", "Selecteer een type" }, option { disabled: true, selected: true, "Selecteer een type" },
option { value: "all", "Iedereen" } option { value: "all", "Iedereen" }
option { value: "group", "Groep" } option { value: "group", "Groep" }
option { value: "day", "Dag" } option { value: "day", "Dag" }
@ -237,19 +262,16 @@ fn TargetValueInput(
let groups = use_context::<crate::Groups>(); let groups = use_context::<crate::Groups>();
let value = use_memo(move || targets.read().get(&id).unwrap().value.clone()); let value = use_memo(move || targets.read().get(&id).unwrap().value.clone());
tracing::info!("Input rendered");
rsx! { rsx! {
match target_kind() { match target_kind() {
TargetKind::Group => { TargetKind::Group => {
rsx! { rsx! {
select { select {
class: "select select-bordered join-item w-2/3", class: "select select-bordered join-item w-1/2",
required: true,
oninput: move |event| { oninput: move |event| {
targets.write().get_mut(&id).unwrap().value = event.value(); targets.write().get_mut(&id).unwrap().value = event.value();
}, },
option { disabled: true, selected: true, value: "", "Selecteer een groep" } option { disabled: true, selected: true, "Selecteer een groep" }
for (group_id, group_name) in groups.0 { for (group_id, group_name) in groups.0 {
option { value: group_id, "{group_name}" } option { value: group_id, "{group_name}" }
} }
@ -259,12 +281,11 @@ fn TargetValueInput(
TargetKind::Day => { TargetKind::Day => {
rsx! { rsx! {
select { select {
class: "select select-bordered join-item w-2/3", class: "select select-bordered join-item w-1/2",
required: true,
oninput: move |event| { oninput: move |event| {
targets.write().get_mut(&id).unwrap().value = event.value(); targets.write().get_mut(&id).unwrap().value = event.value();
}, },
option { disabled: true, selected: true, value: "", "Selecteer een dag" } option { disabled: true, selected: true, "Selecteer een dag" }
option { option {
value: "friday", value: "friday",
"Vrijdag", "Vrijdag",
@ -279,9 +300,8 @@ fn TargetValueInput(
TargetKind::Hourgroup => { TargetKind::Hourgroup => {
rsx! { rsx! {
select { select {
class: "select select-bordered join-item w-1/3", class: "select select-bordered join-item w-1/4",
required: true, option { disabled: true, selected: true, "Selecteer een uur" }
option { disabled: true, selected: true, value: "", "Selecteer een uur" }
option { option {
value: "a", value: "a",
"A", "A",
@ -308,9 +328,8 @@ fn TargetValueInput(
} }
} }
select { select {
class: "select select-bordered join-item w-1/3", class: "select select-bordered join-item w-1/4",
required: true, option { disabled: true, selected: true, "Selecteer een groep" }
option { disabled: true, selected: true, value: "", "Selecteer een groep" }
for i in 1..7 { for i in 1..7 {
option { "{i}" } option { "{i}" }
} }
@ -320,9 +339,8 @@ fn TargetValueInput(
TargetKind::Hour => { TargetKind::Hour => {
rsx! { rsx! {
select { select {
class: "select select-bordered join-item w-2/3", class: "select select-bordered join-item w-1/2",
required: true, option { disabled: true, selected: true, "Selecteer een uur" }
option { disabled: true, selected: true, value: "", "Selecteer een uur" }
option { option {
value: "a", value: "a",
"A", "A",
@ -353,8 +371,8 @@ fn TargetValueInput(
TargetKind::Member | TargetKind::Account => { TargetKind::Member | TargetKind::Account => {
rsx! { rsx! {
input { input {
class: "input input-bordered join-item w-2/3", class: "input input-bordered join-item w-1/2",
required: true, value: "{value}",
oninput: move |event| { oninput: move |event| {
targets.write().get_mut(&id).unwrap().value = event.value(); targets.write().get_mut(&id).unwrap().value = event.value();
}, },
@ -362,7 +380,7 @@ fn TargetValueInput(
} }
}, },
_ => rsx! { _ => rsx! {
div { class: "input input-bordered w-2/3 join-item" } div { class: "input input-bordered w-1/2 join-item" }
} }
} }
} }

View File

@ -8,6 +8,7 @@ mod util;
pub use err::Error; pub use err::Error;
use dioxus::prelude::*; use dioxus::prelude::*;
use tracing::Level;
// Use routes // Use routes
use components::admin::members::migration::Migration; use components::admin::members::migration::Migration;
@ -21,7 +22,6 @@ use components::layout::Global;
use components::news::create::NewsCreate; use components::news::create::NewsCreate;
use components::news::News; use components::news::News;
use components::settings::Settings; use components::settings::Settings;
use tracing::Level;
#[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[rustfmt::skip] #[rustfmt::skip]
@ -49,29 +49,43 @@ pub enum Route {
Migration {}, Migration {},
} }
#[cfg(feature = "server")] const _TAILWIND_URL: &str = manganis::mg!(file("assets/tailwind.css"));
#[tokio::main]
async fn main() {
dioxus::logger::init(Level::INFO).expect("Failed to init logger");
fn main() {
// Init logger
dioxus_logger::init(Level::INFO).expect("failed to init logger");
#[cfg(feature = "server")]
{
use axum::routing::*;
// Spawn main tokio runtime
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {
// Initialize surrealdb connection // Initialize surrealdb connection
util::surrealdb::initialize() util::surrealdb::initialize()
.await .await
.expect("Error while initializing surrealdb"); .expect("Error while initializing surrealdb");
let address = dioxus_cli_config::fullstack_address_or_localhost(); // Create axum app
let app = Router::new()
.serve_dioxus_application(ServeConfig::builder().build(), || {
VirtualDom::new(App)
})
.await;
let router = axum::Router::new().serve_dioxus_application(ServeConfigBuilder::default(), App); // Run axum app
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
let router = router.into_make_service(); axum::serve(listener, app.into_make_service())
let listener = tokio::net::TcpListener::bind(address).await.unwrap(); .await
.unwrap();
axum::serve(listener, router).await.unwrap(); });
} }
#[cfg(not(feature = "server"))] launch(App);
fn main() {
dioxus::launch(App);
} }
#[derive(Clone)] #[derive(Clone)]
@ -103,10 +117,6 @@ fn App() -> Element {
use_context_provider(|| hours); use_context_provider(|| hours);
rsx! { rsx! {
document::Stylesheet {
href: asset!("assets/tailwind.css")
}
div { div {
class: "h-screen flex flex-col", class: "h-screen flex flex-col",
Router::<Route> {} Router::<Route> {}

View File

@ -1,4 +1,3 @@
pub mod member; pub mod member;
pub mod news;
pub mod session; pub mod session;
pub mod user; pub mod user;

View File

@ -1,68 +0,0 @@
use serde::{Deserialize, Serialize};
#[cfg(feature = "server")]
use crate::util::surrealdb::{thing_to_string, DB};
#[cfg(feature = "server")]
use surrealdb::sql::{
statements::{BeginStatement, CommitStatement},
Thing,
};
#[derive(PartialEq, Clone, Copy, Debug, Deserialize, Serialize, Eq)]
pub enum TargetKind {
None,
All,
Group,
Hourgroup,
Hour,
Member,
Account,
Day,
}
impl TargetKind {
pub fn from_string(input: &str) -> Self {
match input {
"all" => Self::All,
"group" => Self::Group,
"hourgroup" => Self::Hourgroup,
"hour" => Self::Hour,
"member" => Self::Member,
"account" => Self::Account,
"day" => Self::Day,
_ => Self::None,
}
}
}
#[derive(PartialEq, Debug, Deserialize, Serialize, Clone, Eq)]
pub struct Target {
pub kind: TargetKind,
pub value: String,
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
pub struct News {
#[cfg_attr(feature = "server", serde(deserialize_with = "thing_to_string"))]
pub id: String,
pub title: String,
pub body: String,
pub tagets: Vec<Target>,
}
#[cfg(feature = "server")]
impl News {
// pub async fn new(
// title: String,
// body: String,
// targets: Vec<Target>,
// ) -> Result<Self, crate::Error> {
// }
}
#[cfg(feature = "server")]
fn fetch_targets(targets: Vec<Target>) {
let mut transaction = DB.query(BeginStatement::default());
for target in targets {}
}

View File

@ -3,6 +3,8 @@ use crate::util::surrealdb::{thing_to_string, DB};
use dioxus::prelude::*; use dioxus::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "server")]
use surrealdb::sql::Thing;
use super::user::User; use super::user::User;
@ -49,7 +51,11 @@ impl Session {
cookie.set_same_site(SameSite::Strict); cookie.set_same_site(SameSite::Strict);
server_context().response_parts_mut().headers.insert( server_context()
.response_parts_mut()
.unwrap()
.headers
.insert(
header::SET_COOKIE, header::SET_COOKIE,
HeaderValue::from_str(&cookie.to_string())?, HeaderValue::from_str(&cookie.to_string())?,
); );
@ -98,7 +104,11 @@ impl Session {
cookie.set_same_site(SameSite::Strict); cookie.set_same_site(SameSite::Strict);
server_context().response_parts_mut().headers.insert( server_context()
.response_parts_mut()
.unwrap()
.headers
.insert(
header::SET_COOKIE, header::SET_COOKIE,
HeaderValue::from_str(&cookie.to_string())?, HeaderValue::from_str(&cookie.to_string())?,
); );

View File

@ -16,11 +16,7 @@ where
} }
pub async fn initialize() -> surrealdb::Result<()> { pub async fn initialize() -> surrealdb::Result<()> {
tracing::info!("Connectiong to surrealdb"); DB.connect::<Ws>("localhost:8000").await?;
DB.connect::<Ws>("localhost:8000")
.await
.expect("Failed to connect to surrealdb");
DB.signin(Root { DB.signin(Root {
username: "root", username: "root",
@ -30,7 +26,7 @@ pub async fn initialize() -> surrealdb::Result<()> {
DB.use_ns("wrbapp").use_db("wrbapp").await?; DB.use_ns("wrbapp").use_db("wrbapp").await?;
apply_queries().await.expect("Failed to apply queries"); apply_queries().await?;
Ok(()) Ok(())
} }
@ -51,8 +47,7 @@ async fn apply_queries() -> surrealdb::Result<()> {
DEFINE INDEX userEmailIndex ON TABLE user COLUMNS email UNIQUE; DEFINE INDEX userEmailIndex ON TABLE user COLUMNS email UNIQUE;
", ",
) )
.await .await?;
.expect("Failed to apply user query");
DB.query( DB.query(
" "