diff --git a/src/app.rs b/src/app.rs
index 247151f..092a64b 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -8,6 +8,7 @@ use crate::components::admin_portal::AdminPortal;
use crate::components::header::Header;
use crate::components::user_menu::MenuOpener;
use crate::pages::login::Login;
+use crate::pages::mail_settings::MailSettings;
use crate::pages::public::Public;
#[derive(Clone, Copy)]
@@ -63,9 +64,9 @@ pub fn App() -> impl IntoView {
// Provides context that manages stylesheets, titles, meta tags, etc.
provide_meta_context();
init_locales();
- provide_context(MenuHelper::new());
- provide_context(MenuOpener::new());
- provide_context(DialogHelper::new());
+ provide_context(MenuHelper::new()); // Only one menu can be opened
+ provide_context(MenuOpener::new()); // Drawer opener
+ provide_context(DialogHelper::new()); // Gray dialog background
view! {
@@ -85,6 +86,11 @@ pub fn App() -> impl IntoView {
}/>
+
+
+
+ }/>
diff --git a/src/backend/data.rs b/src/backend/data.rs
index 2404db4..e578990 100644
--- a/src/backend/data.rs
+++ b/src/backend/data.rs
@@ -538,19 +538,41 @@ pub struct ResSumWithItems {
pub reservations: Vec
}
-/*
+#[derive(Clone, Serialize, Deserialize, Debug, Default)]
+#[cfg_attr(feature = "ssr", derive(sqlx::Type))]
+#[cfg_attr(feature = "ssr", sqlx(type_name = "message_type"))]
pub enum MessageType {
+ #[default]
NewReservation,
NewReservationCust,
ReservationApp,
ReservationCanceled,
}
+impl Display for MessageType {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", match self {
+ MessageType::NewReservation => {"NewReservation"}
+ MessageType::NewReservationCust => {"NewReservationCust"}
+ MessageType::ReservationApp => {"ReservationApp"}
+ MessageType::ReservationCanceled => {"ReservationCanceled"}
+ })
+ }
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug, Default, Validate)]
+#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
pub struct Message {
- id: u16,
- msg_type: MessageType,
- subject: String,
- text: String,
+ id: i32,
+ pub msg_type: MessageType,
+ #[validate(length(min = 1,message = "Enter mail subject"))]
+ pub subject: String,
+ #[validate(length(min = 1,message = "Enter text"))]
+ pub text: String,
}
-*/
+impl Message {
+ pub fn id(&self) -> i32 {
+ self.id
+ }
+}
diff --git a/src/backend/mail.rs b/src/backend/mail.rs
new file mode 100644
index 0000000..4cccd63
--- /dev/null
+++ b/src/backend/mail.rs
@@ -0,0 +1,81 @@
+use cfg_if::cfg_if;
+use leptos::*;
+use validator::Validate;
+use crate::backend::data::{ApiResponse, Message, MessageType};
+use crate::components::data_form::ForValidation;
+
+cfg_if! { if #[cfg(feature = "ssr")] {
+ use sqlx::{PgPool, query, query_as};
+ use sqlx::Error;
+ use crate::backend::get_pool;
+ use crate::error::AppError;
+ use log::info;
+
+ pub async fn message_for_type(msg_type: &MessageType, pool: &PgPool) -> Result {
+ Ok(query_as::<_, Message>("SELECT * FROM message WHERE msg_type = $1")
+ .bind(msg_type)
+ .fetch_one(pool)
+ .await?)
+ }
+
+ pub async fn init_message(msg_type: &MessageType, pool: &PgPool) -> Result<(), Error> {
+ query("INSERT INTO message(msg_type, subject, text) VALUES($1, '', '')")
+ .bind(msg_type)
+ .execute(pool)
+ .await?;
+
+ Ok(())
+ }
+
+ pub async fn check_messages(pool: &PgPool) -> Result<(), AppError> {
+ let types = [
+ MessageType::NewReservation,
+ MessageType::NewReservationCust,
+ MessageType::ReservationApp,
+ MessageType::ReservationCanceled];
+
+ for msg_type in types {
+ let msg = message_for_type(&msg_type, pool).await;
+ if let Err(e) = msg {
+ if matches!(e, Error::RowNotFound) {
+ info!("Creating initial message for type {:?}", msg_type);
+ init_message(&msg_type, pool).await?;
+ } else {
+ return Err(e.into())
+ }
+ }
+ }
+
+ Ok(())
+ }
+}}
+
+#[server]
+pub async fn get_message(msg_type: MessageType) -> Result {
+ let pool = get_pool().await?;
+
+ Ok(message_for_type(&msg_type, &pool).await?)
+}
+
+#[server]
+pub async fn update_message(message: Message) -> Result, ServerFnError> {
+ use crate::perm_check;
+
+ perm_check!(is_admin);
+ let pool = get_pool().await?;
+
+ query("UPDATE message SET subject = $1, text = $2 WHERE msg_type = $3")
+ .bind(message.subject)
+ .bind(message.text)
+ .bind(message.msg_type)
+ .execute(&pool)
+ .await?;
+
+ Ok(ApiResponse::Data(()))
+}
+
+impl ForValidation for UpdateMessage {
+ fn entity(&self) -> &dyn Validate {
+ &self.message
+ }
+}
\ No newline at end of file
diff --git a/src/backend/mod.rs b/src/backend/mod.rs
index 8d34538..1b5d9c4 100644
--- a/src/backend/mod.rs
+++ b/src/backend/mod.rs
@@ -7,6 +7,7 @@ pub mod opening_hours;
pub mod property;
pub mod reservation;
pub mod customer;
+pub mod mail;
#[macro_export]
macro_rules! perm_check {
diff --git a/src/components/admin_portal.rs b/src/components/admin_portal.rs
index c10e7eb..0a70b78 100644
--- a/src/components/admin_portal.rs
+++ b/src/components/admin_portal.rs
@@ -6,9 +6,40 @@ use crate::locales::trl;
use crate::pages::change_pwd::ChangePassword;
use crate::pages::profile_edit::ProfileEdit;
+#[component]
+fn settings_menu(opener: MenuOpener) -> impl IntoView {
+ view! {
+
+ }
+}
+
#[component]
pub fn AdminPortal(children: Children) -> impl IntoView {
let user_menu = MenuOpener::new();
+ let settings_menu = MenuOpener::new();
let (user, set_user) = create_signal(User::default());
let editor = DialogOpener::new();
let pw_changer = DialogOpener::new();
@@ -38,15 +69,15 @@ pub fn AdminPortal(children: Children) -> impl IntoView {