Add default servings to profile + choose servings when scheduling
This commit is contained in:
parent
d8641d4db6
commit
ae6da1a5ae
13 changed files with 70 additions and 34 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
|
@ -327,9 +327,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.9.0"
|
version = "1.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
|
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
|
|
@ -1438,9 +1438,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lettre"
|
name = "lettre"
|
||||||
version = "0.11.11"
|
version = "0.11.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab4c9a167ff73df98a5ecc07e8bf5ce90b583665da3d1762eb1f775ad4d0d6f5"
|
checksum = "e882e1489810a45919477602194312b1a7df0e5acc30a6188be7b520268f63f8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
|
@ -1742,18 +1742,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.8"
|
version = "1.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916"
|
checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pin-project-internal",
|
"pin-project-internal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-internal"
|
name = "pin-project-internal"
|
||||||
version = "1.1.8"
|
version = "1.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb"
|
checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ FROM [UserLoginToken] WHERE [token] = $1
|
||||||
|
|
||||||
pub async fn load_user(&self, user_id: i64) -> Result<Option<model::User>> {
|
pub async fn load_user(&self, user_id: i64) -> Result<Option<model::User>> {
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
"SELECT [id], [email], [name], [lang], [is_admin] FROM [User] WHERE [id] = $1",
|
"SELECT [id], [email], [name], [default_servings], [lang], [is_admin] FROM [User] WHERE [id] = $1",
|
||||||
)
|
)
|
||||||
.bind(user_id)
|
.bind(user_id)
|
||||||
.fetch_optional(&self.pool)
|
.fetch_optional(&self.pool)
|
||||||
|
|
@ -92,13 +92,17 @@ FROM [UserLoginToken] WHERE [token] = $1
|
||||||
user_id: i64,
|
user_id: i64,
|
||||||
new_email: Option<&str>,
|
new_email: Option<&str>,
|
||||||
new_name: Option<&str>,
|
new_name: Option<&str>,
|
||||||
|
new_default_servings: Option<u32>,
|
||||||
new_password: Option<&str>,
|
new_password: Option<&str>,
|
||||||
) -> Result<UpdateUserResult> {
|
) -> Result<UpdateUserResult> {
|
||||||
let mut tx = self.tx().await?;
|
let mut tx = self.tx().await?;
|
||||||
let hashed_new_password = new_password.map(|p| hash(p).unwrap());
|
let hashed_new_password = new_password.map(|p| hash(p).unwrap());
|
||||||
|
|
||||||
let (email, name, hashed_password) = sqlx::query_as::<_, (String, String, String)>(
|
let (email, name, default_servings, hashed_password) = sqlx::query_as::<
|
||||||
"SELECT [email], [name], [password] FROM [User] WHERE [id] = $1",
|
_,
|
||||||
|
(String, String, u32, String),
|
||||||
|
>(
|
||||||
|
"SELECT [email], [name], [default_servings], [password] FROM [User] WHERE [id] = $1",
|
||||||
)
|
)
|
||||||
.bind(user_id)
|
.bind(user_id)
|
||||||
.fetch_one(&mut *tx)
|
.fetch_one(&mut *tx)
|
||||||
|
|
@ -144,13 +148,14 @@ WHERE [id] = $1
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
UPDATE [User]
|
UPDATE [User]
|
||||||
SET [email] = $2, [name] = $3, [password] = $4
|
SET [email] = $2, [name] = $3, [default_servings] = $4, [password] = $5
|
||||||
WHERE [id] = $1
|
WHERE [id] = $1
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(user_id)
|
.bind(user_id)
|
||||||
.bind(new_email.unwrap_or(&email))
|
.bind(new_email.unwrap_or(&email))
|
||||||
.bind(new_name.map(str::trim).unwrap_or(&name))
|
.bind(new_name.map(str::trim).unwrap_or(&name))
|
||||||
|
.bind(new_default_servings.unwrap_or(default_servings))
|
||||||
.bind(hashed_new_password.unwrap_or(hashed_password))
|
.bind(hashed_new_password.unwrap_or(hashed_password))
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ pub struct User {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
|
pub default_servings: u32,
|
||||||
pub lang: String,
|
pub lang: String,
|
||||||
pub is_admin: bool,
|
pub is_admin: bool,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,7 @@ pub struct ProfileTemplate<'a> {
|
||||||
|
|
||||||
pub username: &'a str,
|
pub username: &'a str,
|
||||||
pub email: &'a str,
|
pub email: &'a str,
|
||||||
|
pub default_servings: u32,
|
||||||
pub message: &'a str,
|
pub message: &'a str,
|
||||||
pub message_email: &'a str,
|
pub message_email: &'a str,
|
||||||
pub message_password: &'a str,
|
pub message_password: &'a str,
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ async fn main() {
|
||||||
)
|
)
|
||||||
// Recipes.
|
// Recipes.
|
||||||
.route("/recipe/new", get(services::recipe::create))
|
.route("/recipe/new", get(services::recipe::create))
|
||||||
.route("/recipe/edit/{id}", get(services::recipe::edit_recipe))
|
.route("/recipe/edit/{id}", get(services::recipe::edit))
|
||||||
.route("/recipe/view/{id}", get(services::recipe::view))
|
.route("/recipe/view/{id}", get(services::recipe::view))
|
||||||
// User.
|
// User.
|
||||||
.route(
|
.route(
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ pub async fn create(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub async fn edit_recipe(
|
pub async fn edit(
|
||||||
State(connection): State<db::Connection>,
|
State(connection): State<db::Connection>,
|
||||||
Extension(user): Extension<Option<model::User>>,
|
Extension(user): Extension<Option<model::User>>,
|
||||||
Extension(tr): Extension<translation::Tr>,
|
Extension(tr): Extension<translation::Tr>,
|
||||||
|
|
|
||||||
|
|
@ -655,6 +655,7 @@ pub async fn edit_user_get(
|
||||||
ProfileTemplate {
|
ProfileTemplate {
|
||||||
username: &user.name,
|
username: &user.name,
|
||||||
email: &user.email,
|
email: &user.email,
|
||||||
|
default_servings: user.default_servings,
|
||||||
message: "",
|
message: "",
|
||||||
message_email: "",
|
message_email: "",
|
||||||
message_password: "",
|
message_password: "",
|
||||||
|
|
@ -673,6 +674,7 @@ pub async fn edit_user_get(
|
||||||
pub struct EditUserForm {
|
pub struct EditUserForm {
|
||||||
name: String,
|
name: String,
|
||||||
email: String,
|
email: String,
|
||||||
|
default_servings: u32,
|
||||||
password_1: String,
|
password_1: String,
|
||||||
password_2: String,
|
password_2: String,
|
||||||
}
|
}
|
||||||
|
|
@ -712,6 +714,7 @@ pub async fn edit_user_post(
|
||||||
user: Some(user),
|
user: Some(user),
|
||||||
username: &form_data.name,
|
username: &form_data.name,
|
||||||
email: &form_data.email,
|
email: &form_data.email,
|
||||||
|
default_servings: form_data.default_servings,
|
||||||
message_email: match error {
|
message_email: match error {
|
||||||
ProfileUpdateError::InvalidEmail => tr.t(Sentence::InvalidEmail),
|
ProfileUpdateError::InvalidEmail => tr.t(Sentence::InvalidEmail),
|
||||||
ProfileUpdateError::EmailAlreadyTaken => tr.t(Sentence::EmailAlreadyTaken),
|
ProfileUpdateError::EmailAlreadyTaken => tr.t(Sentence::EmailAlreadyTaken),
|
||||||
|
|
@ -760,6 +763,7 @@ pub async fn edit_user_post(
|
||||||
user.id,
|
user.id,
|
||||||
Some(email_trimmed),
|
Some(email_trimmed),
|
||||||
Some(&form_data.name),
|
Some(&form_data.name),
|
||||||
|
Some(form_data.default_servings),
|
||||||
new_password,
|
new_password,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -815,6 +819,7 @@ pub async fn edit_user_post(
|
||||||
user,
|
user,
|
||||||
username: &form_data.name,
|
username: &form_data.name,
|
||||||
email: &form_data.email,
|
email: &form_data.email,
|
||||||
|
default_servings: form_data.default_servings,
|
||||||
message,
|
message,
|
||||||
message_email: "",
|
message_email: "",
|
||||||
message_password: "",
|
message_password: "",
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ pub enum Sentence {
|
||||||
// Profile
|
// Profile
|
||||||
ProfileTitle,
|
ProfileTitle,
|
||||||
ProfileEmail,
|
ProfileEmail,
|
||||||
|
ProfileDefaultServings,
|
||||||
ProfileNewPassword,
|
ProfileNewPassword,
|
||||||
ProfileFollowEmailLink,
|
ProfileFollowEmailLink,
|
||||||
ProfileEmailSent,
|
ProfileEmailSent,
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
{% block main_container %}
|
{% block main_container %}
|
||||||
|
|
||||||
{% match user %}
|
{% if let Some(user) = user %}
|
||||||
{% when Some with (user) %}
|
|
||||||
|
|
||||||
<div class="content" id="user-edit">
|
<div class="content" id="user-edit">
|
||||||
<h1>{{ tr.t(Sentence::ProfileTitle) }}</h1>
|
<h1>{{ tr.t(Sentence::ProfileTitle) }}</h1>
|
||||||
|
|
@ -27,6 +26,14 @@
|
||||||
|
|
||||||
{{ message_email }}
|
{{ message_email }}
|
||||||
|
|
||||||
|
<label for="input-servings">{{ tr.t(Sentence::ProfileDefaultServings) }}</label>
|
||||||
|
<input
|
||||||
|
id="input-servings"
|
||||||
|
type="number"
|
||||||
|
step="1" min="1" max="100"
|
||||||
|
name="default_servings"
|
||||||
|
value="{{ default_servings }}"/>
|
||||||
|
|
||||||
<label for="input-password-1">{{ tr.tp(Sentence::ProfileNewPassword, [Box::new(common::consts::MIN_PASSWORD_SIZE)]) }}</label>
|
<label for="input-password-1">{{ tr.tp(Sentence::ProfileNewPassword, [Box::new(common::consts::MIN_PASSWORD_SIZE)]) }}</label>
|
||||||
<input id="input-password-1" type="password" name="password_1" autocomplete="new-password" />
|
<input id="input-password-1" type="password" name="password_1" autocomplete="new-password" />
|
||||||
|
|
||||||
|
|
@ -40,7 +47,6 @@
|
||||||
{{ message }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% when None %}
|
{% endif %}
|
||||||
{% endmatch %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
@ -80,7 +80,18 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<div id="hidden-templates">
|
<div id="hidden-templates">
|
||||||
|
{# To create a modal dialog to choose a date and and servings #}
|
||||||
|
{% if let Some(user) = user %}
|
||||||
|
<div class="date-and-servings" >
|
||||||
{% include "calendar.html" %}
|
{% include "calendar.html" %}
|
||||||
|
<label for="input-servings">{{ tr.t(Sentence::RecipeServings) }}</label>
|
||||||
|
<input
|
||||||
|
id="input-servings"
|
||||||
|
type="number"
|
||||||
|
step="1" min="1" max="100"
|
||||||
|
value="{{ user.default_servings }}"/>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@
|
||||||
|
|
||||||
(ProfileTitle, "Profile"),
|
(ProfileTitle, "Profile"),
|
||||||
(ProfileEmail, "Email (need to be revalidated if changed)"),
|
(ProfileEmail, "Email (need to be revalidated if changed)"),
|
||||||
|
(ProfileDefaultServings, "Default servings"),
|
||||||
(ProfileNewPassword, "New password (minimum {} characters)"),
|
(ProfileNewPassword, "New password (minimum {} characters)"),
|
||||||
(ProfileFollowEmailLink, "Follow this link to validate this email address, {}"),
|
(ProfileFollowEmailLink, "Follow this link to validate this email address, {}"),
|
||||||
(ProfileEmailSent, "An email has been sent, follow the link to validate your new email"),
|
(ProfileEmailSent, "An email has been sent, follow the link to validate your new email"),
|
||||||
|
|
@ -188,6 +189,7 @@
|
||||||
|
|
||||||
(ProfileTitle, "Profile"),
|
(ProfileTitle, "Profile"),
|
||||||
(ProfileEmail, "Email (doit être revalidé si changé)"),
|
(ProfileEmail, "Email (doit être revalidé si changé)"),
|
||||||
|
(ProfileDefaultServings, "Nombre de portions par défaut"),
|
||||||
(ProfileNewPassword, "Nouveau mot de passe (minimum {} caractères)"),
|
(ProfileNewPassword, "Nouveau mot de passe (minimum {} caractères)"),
|
||||||
(ProfileFollowEmailLink, "Suivez ce lien pour valider l'adresse email, {}"),
|
(ProfileFollowEmailLink, "Suivez ce lien pour valider l'adresse email, {}"),
|
||||||
(ProfileEmailSent, "Un email a été envoyé, suivez le lien pour valider la nouvelle adresse email"),
|
(ProfileEmailSent, "Un email a été envoyé, suivez le lien pour valider la nouvelle adresse email"),
|
||||||
|
|
@ -198,7 +200,7 @@
|
||||||
(RecipeNotFound, "Recette non-trouvée"),
|
(RecipeNotFound, "Recette non-trouvée"),
|
||||||
(RecipeTitle, "Titre"),
|
(RecipeTitle, "Titre"),
|
||||||
(RecipeDescription, "Description"),
|
(RecipeDescription, "Description"),
|
||||||
(RecipeServings, "Nombre de personnes"),
|
(RecipeServings, "Nombre de portions"),
|
||||||
(RecipeEstimatedTime, "Temps estimé"),
|
(RecipeEstimatedTime, "Temps estimé"),
|
||||||
(RecipeDifficulty, "Difficulté"),
|
(RecipeDifficulty, "Difficulté"),
|
||||||
(RecipeDifficultyEasy, "Facile"),
|
(RecipeDifficultyEasy, "Facile"),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::sync::{Arc, Mutex};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use chrono::{offset::Local, DateTime, Datelike, Days, Months, Weekday};
|
use chrono::{offset::Local, DateTime, Datelike, Days, Months, Weekday};
|
||||||
use common::ron_api;
|
use common::ron_api;
|
||||||
|
|
@ -19,14 +19,14 @@ struct CalendarStateInternal {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct CalendarState {
|
struct CalendarState {
|
||||||
internal_state: Arc<Mutex<CalendarStateInternal>>,
|
internal_state: Rc<RefCell<CalendarStateInternal>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CalendarState {
|
impl CalendarState {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let current_date = Local::now();
|
let current_date = Local::now();
|
||||||
Self {
|
Self {
|
||||||
internal_state: Arc::new(Mutex::new(CalendarStateInternal {
|
internal_state: Rc::new(RefCell::new(CalendarStateInternal {
|
||||||
displayed_date: current_date,
|
displayed_date: current_date,
|
||||||
selected_date: current_date,
|
selected_date: current_date,
|
||||||
})),
|
})),
|
||||||
|
|
@ -34,25 +34,25 @@ impl CalendarState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn displayed_date_next_month(&self) {
|
pub fn displayed_date_next_month(&self) {
|
||||||
let mut locker = self.internal_state.lock().unwrap();
|
let mut state_borrowed = self.internal_state.borrow_mut();
|
||||||
locker.displayed_date = locker.displayed_date + Months::new(1);
|
state_borrowed.displayed_date = state_borrowed.displayed_date + Months::new(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn displayed_date_previous_month(&self) {
|
pub fn displayed_date_previous_month(&self) {
|
||||||
let mut locker = self.internal_state.lock().unwrap();
|
let mut state_borrowed = self.internal_state.borrow_mut();
|
||||||
locker.displayed_date = locker.displayed_date - Months::new(1);
|
state_borrowed.displayed_date = state_borrowed.displayed_date - Months::new(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_displayed_date(&self) -> DateTime<Local> {
|
pub fn get_displayed_date(&self) -> DateTime<Local> {
|
||||||
self.internal_state.lock().unwrap().displayed_date
|
self.internal_state.borrow().displayed_date
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_selected_date(&self) -> DateTime<Local> {
|
pub fn get_selected_date(&self) -> DateTime<Local> {
|
||||||
self.internal_state.lock().unwrap().selected_date
|
self.internal_state.borrow().selected_date
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_selected_date(&self, date: DateTime<Local>) {
|
pub fn set_selected_date(&self, date: DateTime<Local>) {
|
||||||
self.internal_state.lock().unwrap().selected_date = date;
|
self.internal_state.borrow_mut().selected_date = date;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,7 +173,8 @@ fn display_month(calendar: &Element, state: CalendarState) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for recipe in scheduled_recipes.recipes {
|
for recipe in scheduled_recipes.recipes {
|
||||||
log!(recipe.1);
|
// log!(recipe.1);
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,12 @@ pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
|
||||||
let add_to_planner: Element = selector("#recipe-view .add-to-planner");
|
let add_to_planner: Element = selector("#recipe-view .add-to-planner");
|
||||||
EventListener::new(&add_to_planner, "click", move |_event| {
|
EventListener::new(&add_to_planner, "click", move |_event| {
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
modal_dialog::show_and_initialize("#hidden-templates .calendar", async |element| {
|
modal_dialog::show_and_initialize(
|
||||||
calendar::setup(element);
|
"#hidden-templates .date-and-servings",
|
||||||
})
|
async |element| {
|
||||||
|
calendar::setup(element.selector(".calendar"));
|
||||||
|
},
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue