Add a toggle between dark and light theme
This commit is contained in:
parent
d22617538e
commit
559ed139aa
34 changed files with 640 additions and 469 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -4,7 +4,7 @@ backend/data
|
|||
/deploy-to-pi.nu
|
||||
style.css.map
|
||||
backend/static/wasm/*
|
||||
backend/static/style.css
|
||||
backend/static/*.css
|
||||
backend/file.db
|
||||
backend/.sass-cache/*
|
||||
frontend/dist/
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ common = { path = "../common" }
|
|||
|
||||
axum = { version = "0.8", features = ["macros"] }
|
||||
axum-extra = { version = "0.10", features = ["cookie", "query"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio = { version = "1", features = ["signal", "rt-multi-thread"] }
|
||||
tower = { version = "0.5", features = ["util", "limit", "buffer"] }
|
||||
tower-http = { version = "0.6", features = ["fs", "trace"] }
|
||||
|
||||
|
|
@ -44,5 +44,4 @@ lettre = { version = "0.11", default-features = false, features = [
|
|||
"tokio1-rustls-tls",
|
||||
] }
|
||||
|
||||
derive_more = { version = "2", features = ["full"] }
|
||||
thiserror = "2"
|
||||
|
|
|
|||
|
|
@ -26,25 +26,36 @@ where
|
|||
fn main() {
|
||||
println!("cargo:rerun-if-changed=style.scss");
|
||||
|
||||
fn run_sass(command: &mut Command) -> Output {
|
||||
command
|
||||
.arg("--no-source-map")
|
||||
.arg("scss/style.scss")
|
||||
.arg("static/style.css")
|
||||
.output()
|
||||
.expect("Unable to compile SASS file, install SASS, see https://sass-lang.com/")
|
||||
fn run_sass(filename_without_extension: &str) {
|
||||
fn run_sass_command(command: &mut Command, name: &str) -> Output {
|
||||
command
|
||||
.arg("--no-source-map")
|
||||
.arg(format!("scss/{}.scss", name))
|
||||
.arg(format!("static/{}.css", name))
|
||||
.output()
|
||||
.expect("Unable to compile SASS file, install SASS, see https://sass-lang.com/")
|
||||
}
|
||||
|
||||
let output = if exists_in_path("sass.bat") {
|
||||
run_sass_command(
|
||||
Command::new("cmd").args(["/C", "sass.bat"]),
|
||||
filename_without_extension,
|
||||
)
|
||||
} else {
|
||||
run_sass_command(&mut Command::new("sass"), filename_without_extension)
|
||||
};
|
||||
|
||||
if !output.status.success() {
|
||||
// SASS will put the error in the file.
|
||||
let error =
|
||||
std::fs::read_to_string(format!("./static/{}.css", filename_without_extension))
|
||||
.unwrap_or_else(|_| {
|
||||
panic!("unable to read {}.css", filename_without_extension)
|
||||
});
|
||||
panic!("{}", error);
|
||||
}
|
||||
}
|
||||
|
||||
let output = if exists_in_path("sass.bat") {
|
||||
run_sass(Command::new("cmd").args(["/C", "sass.bat"]))
|
||||
} else {
|
||||
run_sass(&mut Command::new("sass"))
|
||||
};
|
||||
|
||||
if !output.status.success() {
|
||||
// SASS will put the error in the file.
|
||||
let error =
|
||||
std::fs::read_to_string("./static/style.css").expect("unable to read style.css");
|
||||
panic!("{}", error);
|
||||
}
|
||||
run_sass("style_light");
|
||||
run_sass("style_dark");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
@use 'modal-dialog.scss';
|
||||
@use 'calendar.scss';
|
||||
|
||||
$dark-theme: false !default;
|
||||
|
||||
$color-1: #B29B89;
|
||||
$color-2: #89B29B;
|
||||
$color-3: #9B89B2;
|
||||
|
|
@ -13,6 +15,17 @@ $text-highlight: color.adjust($color-1, $lightness: +30%);
|
|||
$link-color: color.adjust($color-3, $lightness: -25%);
|
||||
$link-hover-color: color.adjust($color-3, $lightness: +20%);
|
||||
|
||||
@if $dark-theme {
|
||||
$text-color: color.adjust($color-1, $lightness: -10%);
|
||||
$text-highlight: color.adjust($color-1, $lightness: +10%);
|
||||
$link-color: color.adjust($color-3, $lightness: -5%);
|
||||
$link-hover-color: color.adjust($color-3, $lightness: +10%);
|
||||
|
||||
$color-1: color.adjust($color-1, $lightness: -47%);
|
||||
$color-2: color.adjust($color-2, $lightness: -47%);
|
||||
$color-3: color.adjust($color-2, $lightness: -47%);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 5px;
|
||||
padding: 0px;
|
||||
|
|
@ -272,3 +285,81 @@ body {
|
|||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// Customize some form elements.
|
||||
#toggle-theme {
|
||||
// font-size: 17px;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 3.5em;
|
||||
height: 2em;
|
||||
|
||||
// Hide default HTML checkbox
|
||||
input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
// The slider.
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #223243;
|
||||
transition: .4s;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 1.4em;
|
||||
width: 1.4em;
|
||||
border-radius: 20px;
|
||||
left: 0.3em;
|
||||
bottom: 0.3em;
|
||||
background-color: #223243;
|
||||
box-shadow: inset 2px -2px 0 1.8px #fff;
|
||||
transition: .4s;
|
||||
animation: anima1 0.3s linear;
|
||||
}
|
||||
|
||||
@keyframes anima1 {
|
||||
0% {
|
||||
transform: translateX(1.5em);
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: translateX(-0.3em);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(0em);
|
||||
}
|
||||
}
|
||||
|
||||
input:checked+.slider:before {
|
||||
background-color: yellow;
|
||||
box-shadow: none;
|
||||
transform: translateX(1.5em);
|
||||
animation: anima 0.3s linear;
|
||||
}
|
||||
|
||||
@keyframes anima {
|
||||
0% {
|
||||
transform: translateX(0px)
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: translateX(1.6em)
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(1.4em)
|
||||
}
|
||||
}
|
||||
}
|
||||
1
backend/scss/style_dark.scss
Normal file
1
backend/scss/style_dark.scss
Normal file
|
|
@ -0,0 +1 @@
|
|||
@use 'main.scss' with ($dark-theme: true);
|
||||
1
backend/scss/style_light.scss
Normal file
1
backend/scss/style_light.scss
Normal file
|
|
@ -0,0 +1 @@
|
|||
@use 'main.scss' with ($dark-theme: false);
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
use askama::Template;
|
||||
|
||||
use crate::{
|
||||
Context,
|
||||
data::model,
|
||||
translation::{self, Sentence, Tr},
|
||||
};
|
||||
|
|
@ -20,8 +21,7 @@ impl Recipes {
|
|||
#[derive(Template)]
|
||||
#[template(path = "home.html")]
|
||||
pub struct HomeTemplate {
|
||||
pub user: Option<model::User>,
|
||||
pub tr: Tr,
|
||||
pub context: Context,
|
||||
|
||||
pub recipes: Recipes,
|
||||
}
|
||||
|
|
@ -29,8 +29,7 @@ pub struct HomeTemplate {
|
|||
#[derive(Template)]
|
||||
#[template(path = "message.html")]
|
||||
pub struct MessageTemplate<'a> {
|
||||
pub user: Option<model::User>,
|
||||
pub tr: Tr,
|
||||
pub context: Context,
|
||||
|
||||
pub message: &'a str,
|
||||
pub as_code: bool, // Display the message in <pre> markup.
|
||||
|
|
@ -39,8 +38,11 @@ pub struct MessageTemplate<'a> {
|
|||
impl<'a> MessageTemplate<'a> {
|
||||
pub fn new(message: &'a str, tr: Tr) -> MessageTemplate<'a> {
|
||||
MessageTemplate {
|
||||
user: None,
|
||||
tr,
|
||||
context: Context {
|
||||
user: None,
|
||||
tr,
|
||||
dark_theme: false,
|
||||
},
|
||||
message,
|
||||
as_code: false,
|
||||
}
|
||||
|
|
@ -52,8 +54,11 @@ impl<'a> MessageTemplate<'a> {
|
|||
user: Option<model::User>,
|
||||
) -> MessageTemplate<'a> {
|
||||
MessageTemplate {
|
||||
user,
|
||||
tr,
|
||||
context: Context {
|
||||
user,
|
||||
tr,
|
||||
dark_theme: false,
|
||||
},
|
||||
message,
|
||||
as_code: false,
|
||||
}
|
||||
|
|
@ -63,8 +68,7 @@ impl<'a> MessageTemplate<'a> {
|
|||
#[derive(Template)]
|
||||
#[template(path = "sign_up_form.html")]
|
||||
pub struct SignUpFormTemplate<'a> {
|
||||
pub user: Option<model::User>,
|
||||
pub tr: Tr,
|
||||
pub context: Context,
|
||||
|
||||
pub email: String,
|
||||
pub message: &'a str,
|
||||
|
|
@ -75,8 +79,7 @@ pub struct SignUpFormTemplate<'a> {
|
|||
#[derive(Template)]
|
||||
#[template(path = "sign_in_form.html")]
|
||||
pub struct SignInFormTemplate<'a> {
|
||||
pub user: Option<model::User>,
|
||||
pub tr: Tr,
|
||||
pub context: Context,
|
||||
|
||||
pub email: &'a str,
|
||||
pub message: &'a str,
|
||||
|
|
@ -85,8 +88,7 @@ pub struct SignInFormTemplate<'a> {
|
|||
#[derive(Template)]
|
||||
#[template(path = "ask_reset_password.html")]
|
||||
pub struct AskResetPasswordTemplate<'a> {
|
||||
pub user: Option<model::User>,
|
||||
pub tr: Tr,
|
||||
pub context: Context,
|
||||
|
||||
pub email: &'a str,
|
||||
pub message: &'a str,
|
||||
|
|
@ -96,8 +98,7 @@ pub struct AskResetPasswordTemplate<'a> {
|
|||
#[derive(Template)]
|
||||
#[template(path = "reset_password.html")]
|
||||
pub struct ResetPasswordTemplate<'a> {
|
||||
pub user: Option<model::User>,
|
||||
pub tr: Tr,
|
||||
pub context: Context,
|
||||
|
||||
pub reset_token: &'a str,
|
||||
pub message: &'a str,
|
||||
|
|
@ -107,8 +108,7 @@ pub struct ResetPasswordTemplate<'a> {
|
|||
#[derive(Template)]
|
||||
#[template(path = "profile.html")]
|
||||
pub struct ProfileTemplate<'a> {
|
||||
pub user: Option<model::User>,
|
||||
pub tr: Tr,
|
||||
pub context: Context,
|
||||
|
||||
pub username: &'a str,
|
||||
pub email: &'a str,
|
||||
|
|
@ -121,8 +121,7 @@ pub struct ProfileTemplate<'a> {
|
|||
#[derive(Template)]
|
||||
#[template(path = "recipe_view.html")]
|
||||
pub struct RecipeViewTemplate {
|
||||
pub user: Option<model::User>,
|
||||
pub tr: Tr,
|
||||
pub context: Context,
|
||||
|
||||
pub recipes: Recipes,
|
||||
|
||||
|
|
@ -132,8 +131,7 @@ pub struct RecipeViewTemplate {
|
|||
#[derive(Template)]
|
||||
#[template(path = "recipe_edit.html")]
|
||||
pub struct RecipeEditTemplate {
|
||||
pub user: Option<model::User>,
|
||||
pub tr: Tr,
|
||||
pub context: Context,
|
||||
|
||||
pub recipes: Recipes,
|
||||
|
||||
|
|
@ -143,7 +141,7 @@ pub struct RecipeEditTemplate {
|
|||
#[derive(Template)]
|
||||
#[template(path = "recipes_list_fragment.html")]
|
||||
pub struct RecipesListFragmentTemplate {
|
||||
pub tr: Tr,
|
||||
pub context: Context,
|
||||
|
||||
pub recipes: Recipes,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,13 @@ const TRACING_LEVEL: tracing::Level = tracing::Level::DEBUG;
|
|||
#[cfg(not(debug_assertions))]
|
||||
const TRACING_LEVEL: tracing::Level = tracing::Level::INFO;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Context {
|
||||
pub user: Option<model::User>,
|
||||
pub tr: Tr,
|
||||
pub dark_theme: bool,
|
||||
}
|
||||
|
||||
// TODO: Should main returns 'Result'?
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
|
@ -294,11 +301,7 @@ async fn main() {
|
|||
.merge(html_routes)
|
||||
.nest("/ron-api", ron_api_routes)
|
||||
.fallback(services::not_found)
|
||||
.layer(middleware::from_fn(translation))
|
||||
.layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
user_authentication,
|
||||
))
|
||||
.layer(middleware::from_fn_with_state(state.clone(), context))
|
||||
.with_state(state)
|
||||
.nest_service("/favicon.ico", ServeFile::new("static/favicon.ico"))
|
||||
.nest_service("/static", ServeDir::new("static"))
|
||||
|
|
@ -321,19 +324,6 @@ async fn main() {
|
|||
event!(Level::INFO, "Recipes stopped");
|
||||
}
|
||||
|
||||
async fn user_authentication(
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
State(connection): State<db::Connection>,
|
||||
mut req: Request,
|
||||
next: Next,
|
||||
) -> Result<Response> {
|
||||
let jar = CookieJar::from_headers(req.headers());
|
||||
let (client_ip, client_user_agent) = utils::get_ip_and_user_agent(req.headers(), addr);
|
||||
let user = get_current_user(connection, &jar, &client_ip, &client_user_agent).await;
|
||||
req.extensions_mut().insert(user);
|
||||
Ok(next.run(req).await)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Lang(Option<String>);
|
||||
|
||||
|
|
@ -384,16 +374,21 @@ fn url_rewriting(mut req: Request) -> Request {
|
|||
/// - Get from the cookie.
|
||||
/// - Get from the HTTP header `accept-language`.
|
||||
/// - Set as `translation::DEFAULT_LANGUAGE_CODE`.
|
||||
async fn translation(
|
||||
Extension(lang): Extension<Lang>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
async fn context(
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(lang_from_url): Extension<Lang>,
|
||||
mut req: Request,
|
||||
next: Next,
|
||||
) -> Result<Response> {
|
||||
let language = if let Some(lang) = lang.0 {
|
||||
let jar = CookieJar::from_headers(req.headers());
|
||||
let (client_ip, client_user_agent) = utils::get_ip_and_user_agent(req.headers(), addr);
|
||||
let user = get_current_user(connection, &jar, &client_ip, &client_user_agent).await;
|
||||
|
||||
let language = if let Some(lang) = lang_from_url.0 {
|
||||
lang
|
||||
} else if let Some(user) = user {
|
||||
user.lang
|
||||
} else if let Some(ref user) = user {
|
||||
user.lang.clone()
|
||||
} else {
|
||||
let available_codes = translation::available_codes();
|
||||
let jar = CookieJar::from_headers(req.headers());
|
||||
|
|
@ -420,7 +415,17 @@ async fn translation(
|
|||
|
||||
let tr = Tr::new(&language);
|
||||
|
||||
req.extensions_mut().insert(tr);
|
||||
let dark_theme = match jar.get(common::consts::COOKIE_DARK_THEME) {
|
||||
Some(dark_theme_cookie) => dark_theme_cookie.value().parse().unwrap_or_default(),
|
||||
None => false,
|
||||
};
|
||||
|
||||
req.extensions_mut().insert(Context {
|
||||
user,
|
||||
tr,
|
||||
dark_theme,
|
||||
});
|
||||
|
||||
Ok(next.run(req).await)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,7 @@ use axum::{
|
|||
use serde::Deserialize;
|
||||
// use tracing::{event, Level};
|
||||
|
||||
use crate::{
|
||||
Result,
|
||||
data::{db, model},
|
||||
html_templates::*,
|
||||
translation,
|
||||
};
|
||||
use crate::{Context, Result, data::db, html_templates::*};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CurrentRecipeId {
|
||||
|
|
@ -23,14 +18,16 @@ pub struct CurrentRecipeId {
|
|||
pub async fn recipes_list_fragments(
|
||||
State(connection): State<db::Connection>,
|
||||
current_recipe: Query<CurrentRecipeId>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
let recipes = Recipes {
|
||||
published: connection
|
||||
.get_all_published_recipe_titles(tr.current_lang_code(), user.as_ref().map(|u| u.id))
|
||||
.get_all_published_recipe_titles(
|
||||
context.tr.current_lang_code(),
|
||||
context.user.as_ref().map(|u| u.id),
|
||||
)
|
||||
.await?,
|
||||
unpublished: if let Some(user) = user.as_ref() {
|
||||
unpublished: if let Some(user) = context.user.as_ref() {
|
||||
connection
|
||||
.get_all_unpublished_recipe_titles(user.id)
|
||||
.await?
|
||||
|
|
@ -39,5 +36,7 @@ pub async fn recipes_list_fragments(
|
|||
},
|
||||
current_id: current_recipe.current_recipe_id,
|
||||
};
|
||||
Ok(Html(RecipesListFragmentTemplate { tr, recipes }.render()?))
|
||||
Ok(Html(
|
||||
RecipesListFragmentTemplate { context, recipes }.render()?,
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,7 @@ use axum::{
|
|||
response::{Html, IntoResponse, Response},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Result,
|
||||
data::{db, model},
|
||||
html_templates::*,
|
||||
ron_utils, translation,
|
||||
};
|
||||
use crate::{Context, Result, data::db, html_templates::*, ron_utils};
|
||||
|
||||
pub mod fragments;
|
||||
pub mod recipe;
|
||||
|
|
@ -21,7 +16,7 @@ pub mod user;
|
|||
|
||||
// Will embed RON error in HTML page.
|
||||
pub async fn ron_error_to_html(
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
req: Request,
|
||||
next: Next,
|
||||
) -> Result<Response> {
|
||||
|
|
@ -35,10 +30,9 @@ pub async fn ron_error_to_html(
|
|||
};
|
||||
return Ok(Html(
|
||||
MessageTemplate {
|
||||
user: None,
|
||||
context,
|
||||
message: &message,
|
||||
as_code: true,
|
||||
tr,
|
||||
}
|
||||
.render()?,
|
||||
)
|
||||
|
|
@ -54,14 +48,16 @@ pub async fn ron_error_to_html(
|
|||
#[debug_handler]
|
||||
pub async fn home_page(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
let recipes = Recipes {
|
||||
published: connection
|
||||
.get_all_published_recipe_titles(tr.current_lang_code(), user.as_ref().map(|u| u.id))
|
||||
.get_all_published_recipe_titles(
|
||||
context.tr.current_lang_code(),
|
||||
context.user.as_ref().map(|u| u.id),
|
||||
)
|
||||
.await?,
|
||||
unpublished: if let Some(user) = user.as_ref() {
|
||||
unpublished: if let Some(user) = context.user.as_ref() {
|
||||
connection
|
||||
.get_all_unpublished_recipe_titles(user.id)
|
||||
.await?
|
||||
|
|
@ -71,18 +67,15 @@ pub async fn home_page(
|
|||
current_id: None,
|
||||
};
|
||||
|
||||
Ok(Html(HomeTemplate { user, recipes, tr }.render()?))
|
||||
Ok(Html(HomeTemplate { context, recipes }.render()?))
|
||||
}
|
||||
|
||||
///// 404 /////
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn not_found(
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
pub async fn not_found(Extension(context): Extension<Context>) -> Result<impl IntoResponse> {
|
||||
Ok((
|
||||
StatusCode::NOT_FOUND,
|
||||
Html(MessageTemplate::new_with_user("404: Not found", tr, user).render()?),
|
||||
Html(MessageTemplate::new_with_user("404: Not found", context.tr, context.user).render()?),
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,44 +7,48 @@ use axum::{
|
|||
// use tracing::{event, Level};
|
||||
|
||||
use crate::{
|
||||
Result,
|
||||
Context, Result,
|
||||
data::{db, model},
|
||||
html_templates::*,
|
||||
translation::{self, Sentence},
|
||||
translation::Sentence,
|
||||
};
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn create(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
) -> Result<Response> {
|
||||
if let Some(user) = user {
|
||||
if let Some(user) = context.user {
|
||||
let recipe_id = connection.create_recipe(user.id).await?;
|
||||
Ok(Redirect::to(&format!(
|
||||
"/{}/recipe/edit/{}",
|
||||
tr.current_lang_code(),
|
||||
context.tr.current_lang_code(),
|
||||
recipe_id
|
||||
))
|
||||
.into_response())
|
||||
} else {
|
||||
Ok(Html(MessageTemplate::new(tr.t(Sentence::NotLoggedIn), tr).render()?).into_response())
|
||||
Ok(
|
||||
Html(MessageTemplate::new(context.tr.t(Sentence::NotLoggedIn), context.tr).render()?)
|
||||
.into_response(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn edit(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
Path(recipe_id): Path<i64>,
|
||||
) -> Result<Response> {
|
||||
if let Some(user) = user {
|
||||
if let Some(ref user) = context.user {
|
||||
if let Some(recipe) = connection.get_recipe(recipe_id, false).await? {
|
||||
if model::can_user_edit_recipe(&user, &recipe) {
|
||||
let recipes = Recipes {
|
||||
published: connection
|
||||
.get_all_published_recipe_titles(tr.current_lang_code(), Some(user.id))
|
||||
.get_all_published_recipe_titles(
|
||||
context.tr.current_lang_code(),
|
||||
Some(user.id),
|
||||
)
|
||||
.await?,
|
||||
unpublished: connection
|
||||
.get_all_unpublished_recipe_titles(user.id)
|
||||
|
|
@ -54,8 +58,7 @@ pub async fn edit(
|
|||
|
||||
Ok(Html(
|
||||
RecipeEditTemplate {
|
||||
user: Some(user),
|
||||
tr,
|
||||
context,
|
||||
recipes,
|
||||
recipe,
|
||||
}
|
||||
|
|
@ -63,42 +66,48 @@ pub async fn edit(
|
|||
)
|
||||
.into_response())
|
||||
} else {
|
||||
Ok(
|
||||
Html(
|
||||
MessageTemplate::new(tr.t(Sentence::RecipeNotAllowedToEdit), tr)
|
||||
.render()?,
|
||||
Ok(Html(
|
||||
MessageTemplate::new(
|
||||
context.tr.t(Sentence::RecipeNotAllowedToEdit),
|
||||
context.tr,
|
||||
)
|
||||
.into_response(),
|
||||
.render()?,
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
} else {
|
||||
Ok(
|
||||
Html(MessageTemplate::new(tr.t(Sentence::RecipeNotFound), tr).render()?)
|
||||
.into_response(),
|
||||
Ok(Html(
|
||||
MessageTemplate::new(context.tr.t(Sentence::RecipeNotFound), context.tr)
|
||||
.render()?,
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
} else {
|
||||
Ok(Html(MessageTemplate::new(tr.t(Sentence::NotLoggedIn), tr).render()?).into_response())
|
||||
Ok(
|
||||
Html(MessageTemplate::new(context.tr.t(Sentence::NotLoggedIn), context.tr).render()?)
|
||||
.into_response(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn view(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
Path(recipe_id): Path<i64>,
|
||||
) -> Result<Response> {
|
||||
match connection.get_recipe(recipe_id, true).await? {
|
||||
Some(recipe) => {
|
||||
if !recipe.is_published
|
||||
&& (user.is_none() || recipe.user_id != user.as_ref().unwrap().id)
|
||||
&& (context.user.is_none() || recipe.user_id != context.user.as_ref().unwrap().id)
|
||||
{
|
||||
return Ok(Html(
|
||||
MessageTemplate::new_with_user(
|
||||
&tr.tp(Sentence::RecipeNotAllowedToView, &[Box::new(recipe_id)]),
|
||||
tr,
|
||||
user,
|
||||
&context
|
||||
.tr
|
||||
.tp(Sentence::RecipeNotAllowedToView, &[Box::new(recipe_id)]),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
)
|
||||
|
|
@ -108,11 +117,11 @@ pub async fn view(
|
|||
let recipes = Recipes {
|
||||
published: connection
|
||||
.get_all_published_recipe_titles(
|
||||
tr.current_lang_code(),
|
||||
user.as_ref().map(|u| u.id),
|
||||
context.tr.current_lang_code(),
|
||||
context.user.as_ref().map(|u| u.id),
|
||||
)
|
||||
.await?,
|
||||
unpublished: if let Some(user) = user.as_ref() {
|
||||
unpublished: if let Some(user) = context.user.as_ref() {
|
||||
connection
|
||||
.get_all_unpublished_recipe_titles(user.id)
|
||||
.await?
|
||||
|
|
@ -124,8 +133,7 @@ pub async fn view(
|
|||
|
||||
Ok(Html(
|
||||
RecipeViewTemplate {
|
||||
user,
|
||||
tr,
|
||||
context,
|
||||
recipes,
|
||||
recipe,
|
||||
}
|
||||
|
|
@ -134,7 +142,12 @@ pub async fn view(
|
|||
.into_response())
|
||||
}
|
||||
None => Ok(Html(
|
||||
MessageTemplate::new_with_user(tr.t(Sentence::RecipeNotFound), tr, user).render()?,
|
||||
MessageTemplate::new_with_user(
|
||||
context.tr.t(Sentence::RecipeNotFound),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
)
|
||||
.into_response()),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ use axum_extra::extract::Query;
|
|||
// use tracing::{event, Level};
|
||||
|
||||
use crate::{
|
||||
Context,
|
||||
data::{self, db},
|
||||
model,
|
||||
ron_extractor::ExtractRon,
|
||||
ron_utils::{ron_error, ron_response_ok},
|
||||
};
|
||||
|
|
@ -19,10 +19,10 @@ use super::rights::*;
|
|||
#[debug_handler]
|
||||
pub async fn get_scheduled_recipes(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
date_range: Query<common::ron_api::DateRange>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
if let Some(user) = user {
|
||||
if let Some(user) = context.user {
|
||||
Ok(ron_response_ok(common::ron_api::ScheduledRecipes {
|
||||
recipes: connection
|
||||
.get_scheduled_recipes(user.id, date_range.start_date, date_range.end_date)
|
||||
|
|
@ -50,11 +50,11 @@ impl From<data::db::recipe::AddScheduledRecipeResult> for common::ron_api::Sched
|
|||
#[debug_handler]
|
||||
pub async fn schedule_recipe(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::ScheduleRecipe>,
|
||||
) -> Result<Response> {
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
if let Some(user) = user {
|
||||
check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?;
|
||||
if let Some(user) = context.user {
|
||||
connection
|
||||
.add_scheduled_recipe(
|
||||
user.id,
|
||||
|
|
@ -76,11 +76,11 @@ pub async fn schedule_recipe(
|
|||
#[debug_handler]
|
||||
pub async fn rm_scheduled_recipe(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::RemoveScheduledRecipe>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
if let Some(user) = user {
|
||||
check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?;
|
||||
if let Some(user) = context.user {
|
||||
connection
|
||||
.rm_scheduled_recipe(
|
||||
user.id,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use axum::{
|
|||
use axum_extra::extract::cookie::{Cookie, CookieJar};
|
||||
// use tracing::{event, Level};
|
||||
|
||||
use crate::{consts, data::db, model, ron_extractor::ExtractRon, ron_utils::ron_error};
|
||||
use crate::{Context, consts, data::db, model, ron_extractor::ExtractRon, ron_utils::ron_error};
|
||||
|
||||
pub mod calendar;
|
||||
pub mod recipe;
|
||||
|
|
@ -19,12 +19,12 @@ const NOT_AUTHORIZED_MESSAGE: &str = "Action not authorized";
|
|||
#[debug_handler]
|
||||
pub async fn set_lang(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
headers: HeaderMap,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetLang>,
|
||||
) -> Result<(CookieJar, StatusCode)> {
|
||||
let mut jar = CookieJar::from_headers(&headers);
|
||||
if let Some(user) = user {
|
||||
if let Some(user) = context.user {
|
||||
connection.set_user_lang(user.id, &ron.lang).await?;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use axum_extra::extract::Query;
|
|||
use common::ron_api;
|
||||
// use tracing::{event, Level};
|
||||
|
||||
use crate::{data::db, model, ron_extractor::ExtractRon, ron_utils::ron_response_ok};
|
||||
use crate::{Context, data::db, model, ron_extractor::ExtractRon, ron_utils::ron_response_ok};
|
||||
|
||||
use super::rights::*;
|
||||
|
||||
|
|
@ -27,10 +27,10 @@ pub async fn get_titles(
|
|||
#[debug_handler]
|
||||
pub async fn set_title(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::SetRecipeTitle>,
|
||||
) -> Result<StatusCode> {
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?;
|
||||
connection
|
||||
.set_recipe_title(ron.recipe_id, &ron.title)
|
||||
.await?;
|
||||
|
|
@ -40,10 +40,10 @@ pub async fn set_title(
|
|||
#[debug_handler]
|
||||
pub async fn set_description(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::SetRecipeDescription>,
|
||||
) -> Result<StatusCode> {
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?;
|
||||
connection
|
||||
.set_recipe_description(ron.recipe_id, &ron.description)
|
||||
.await?;
|
||||
|
|
@ -53,10 +53,10 @@ pub async fn set_description(
|
|||
#[debug_handler]
|
||||
pub async fn set_servings(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::SetRecipeServings>,
|
||||
) -> Result<StatusCode> {
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?;
|
||||
connection
|
||||
.set_recipe_servings(ron.recipe_id, ron.servings)
|
||||
.await?;
|
||||
|
|
@ -66,10 +66,10 @@ pub async fn set_servings(
|
|||
#[debug_handler]
|
||||
pub async fn set_estimated_time(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::SetRecipeEstimatedTime>,
|
||||
) -> Result<StatusCode> {
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?;
|
||||
connection
|
||||
.set_recipe_estimated_time(ron.recipe_id, ron.estimated_time)
|
||||
.await?;
|
||||
|
|
@ -90,10 +90,10 @@ pub async fn get_tags(
|
|||
#[debug_handler]
|
||||
pub async fn add_tags(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::Tags>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?;
|
||||
connection
|
||||
.add_recipe_tags(
|
||||
ron.recipe_id,
|
||||
|
|
@ -109,10 +109,10 @@ pub async fn add_tags(
|
|||
#[debug_handler]
|
||||
pub async fn rm_tags(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::Tags>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?;
|
||||
connection.rm_recipe_tags(ron.recipe_id, &ron.tags).await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
|
@ -120,10 +120,10 @@ pub async fn rm_tags(
|
|||
#[debug_handler]
|
||||
pub async fn set_difficulty(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::SetRecipeDifficulty>,
|
||||
) -> Result<StatusCode> {
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?;
|
||||
connection
|
||||
.set_recipe_difficulty(ron.recipe_id, ron.difficulty)
|
||||
.await?;
|
||||
|
|
@ -133,7 +133,7 @@ pub async fn set_difficulty(
|
|||
#[debug_handler]
|
||||
pub async fn set_language(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::SetRecipeLanguage>,
|
||||
) -> Result<StatusCode> {
|
||||
if !crate::translation::available_codes()
|
||||
|
|
@ -144,7 +144,7 @@ pub async fn set_language(
|
|||
return Ok(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?;
|
||||
connection
|
||||
.set_recipe_language(ron.recipe_id, &ron.lang)
|
||||
.await?;
|
||||
|
|
@ -154,10 +154,10 @@ pub async fn set_language(
|
|||
#[debug_handler]
|
||||
pub async fn set_is_published(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::SetIsPublished>,
|
||||
) -> Result<StatusCode> {
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?;
|
||||
connection
|
||||
.set_recipe_is_published(ron.recipe_id, ron.is_published)
|
||||
.await?;
|
||||
|
|
@ -167,10 +167,10 @@ pub async fn set_is_published(
|
|||
#[debug_handler]
|
||||
pub async fn rm(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::Id>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe(&connection, &user, ron.id).await?;
|
||||
check_user_rights_recipe(&connection, &context.user, ron.id).await?;
|
||||
connection.rm_recipe(ron.id).await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
|
@ -231,10 +231,10 @@ pub async fn get_groups(
|
|||
#[debug_handler]
|
||||
pub async fn add_group(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::Id>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe(&connection, &user, ron.id).await?;
|
||||
check_user_rights_recipe(&connection, &context.user, ron.id).await?;
|
||||
let id = connection.add_recipe_group(ron.id).await?;
|
||||
|
||||
Ok(ron_response_ok(ron_api::Id { id }))
|
||||
|
|
@ -243,10 +243,10 @@ pub async fn add_group(
|
|||
#[debug_handler]
|
||||
pub async fn rm_group(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::Id>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_group(&connection, &user, ron.id).await?;
|
||||
check_user_rights_recipe_group(&connection, &context.user, ron.id).await?;
|
||||
connection.rm_recipe_group(ron.id).await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
|
@ -254,10 +254,10 @@ pub async fn rm_group(
|
|||
#[debug_handler]
|
||||
pub async fn set_group_name(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::SetGroupName>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_group(&connection, &user, ron.group_id).await?;
|
||||
check_user_rights_recipe_group(&connection, &context.user, ron.group_id).await?;
|
||||
connection.set_group_name(ron.group_id, &ron.name).await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
|
@ -265,10 +265,10 @@ pub async fn set_group_name(
|
|||
#[debug_handler]
|
||||
pub async fn set_group_comment(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::SetGroupComment>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_group(&connection, &user, ron.group_id).await?;
|
||||
check_user_rights_recipe_group(&connection, &context.user, ron.group_id).await?;
|
||||
connection
|
||||
.set_group_comment(ron.group_id, &ron.comment)
|
||||
.await?;
|
||||
|
|
@ -278,10 +278,10 @@ pub async fn set_group_comment(
|
|||
#[debug_handler]
|
||||
pub async fn set_groups_order(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::Ids>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_groups(&connection, &user, &ron.ids).await?;
|
||||
check_user_rights_recipe_groups(&connection, &context.user, &ron.ids).await?;
|
||||
connection.set_groups_order(&ron.ids).await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
|
@ -289,10 +289,10 @@ pub async fn set_groups_order(
|
|||
#[debug_handler]
|
||||
pub async fn add_step(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::Id>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_group(&connection, &user, ron.id).await?;
|
||||
check_user_rights_recipe_group(&connection, &context.user, ron.id).await?;
|
||||
let id = connection.add_recipe_step(ron.id).await?;
|
||||
|
||||
Ok(ron_response_ok(ron_api::Id { id }))
|
||||
|
|
@ -301,10 +301,10 @@ pub async fn add_step(
|
|||
#[debug_handler]
|
||||
pub async fn rm_step(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::Id>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_step(&connection, &user, ron.id).await?;
|
||||
check_user_rights_recipe_step(&connection, &context.user, ron.id).await?;
|
||||
connection.rm_recipe_step(ron.id).await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
|
@ -312,10 +312,10 @@ pub async fn rm_step(
|
|||
#[debug_handler]
|
||||
pub async fn set_step_action(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::SetStepAction>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_step(&connection, &user, ron.step_id).await?;
|
||||
check_user_rights_recipe_step(&connection, &context.user, ron.step_id).await?;
|
||||
connection.set_step_action(ron.step_id, &ron.action).await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
|
@ -323,10 +323,10 @@ pub async fn set_step_action(
|
|||
#[debug_handler]
|
||||
pub async fn set_steps_order(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::Ids>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_steps(&connection, &user, &ron.ids).await?;
|
||||
check_user_rights_recipe_steps(&connection, &context.user, &ron.ids).await?;
|
||||
connection.set_steps_order(&ron.ids).await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
|
@ -334,10 +334,10 @@ pub async fn set_steps_order(
|
|||
#[debug_handler]
|
||||
pub async fn add_ingredient(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::Id>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_step(&connection, &user, ron.id).await?;
|
||||
check_user_rights_recipe_step(&connection, &context.user, ron.id).await?;
|
||||
let id = connection.add_recipe_ingredient(ron.id).await?;
|
||||
Ok(ron_response_ok(ron_api::Id { id }))
|
||||
}
|
||||
|
|
@ -345,10 +345,10 @@ pub async fn add_ingredient(
|
|||
#[debug_handler]
|
||||
pub async fn rm_ingredient(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::Id>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_ingredient(&connection, &user, ron.id).await?;
|
||||
check_user_rights_recipe_ingredient(&connection, &context.user, ron.id).await?;
|
||||
connection.rm_recipe_ingredient(ron.id).await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
|
@ -356,10 +356,10 @@ pub async fn rm_ingredient(
|
|||
#[debug_handler]
|
||||
pub async fn set_ingredient_name(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::SetIngredientName>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?;
|
||||
check_user_rights_recipe_ingredient(&connection, &context.user, ron.ingredient_id).await?;
|
||||
connection
|
||||
.set_ingredient_name(ron.ingredient_id, &ron.name)
|
||||
.await?;
|
||||
|
|
@ -369,10 +369,10 @@ pub async fn set_ingredient_name(
|
|||
#[debug_handler]
|
||||
pub async fn set_ingredient_comment(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::SetIngredientComment>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?;
|
||||
check_user_rights_recipe_ingredient(&connection, &context.user, ron.ingredient_id).await?;
|
||||
connection
|
||||
.set_ingredient_comment(ron.ingredient_id, &ron.comment)
|
||||
.await?;
|
||||
|
|
@ -382,10 +382,10 @@ pub async fn set_ingredient_comment(
|
|||
#[debug_handler]
|
||||
pub async fn set_ingredient_quantity(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::SetIngredientQuantity>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?;
|
||||
check_user_rights_recipe_ingredient(&connection, &context.user, ron.ingredient_id).await?;
|
||||
connection
|
||||
.set_ingredient_quantity(ron.ingredient_id, ron.quantity)
|
||||
.await?;
|
||||
|
|
@ -395,10 +395,10 @@ pub async fn set_ingredient_quantity(
|
|||
#[debug_handler]
|
||||
pub async fn set_ingredient_unit(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::SetIngredientUnit>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?;
|
||||
check_user_rights_recipe_ingredient(&connection, &context.user, ron.ingredient_id).await?;
|
||||
connection
|
||||
.set_ingredient_unit(ron.ingredient_id, &ron.unit)
|
||||
.await?;
|
||||
|
|
@ -408,10 +408,10 @@ pub async fn set_ingredient_unit(
|
|||
#[debug_handler]
|
||||
pub async fn set_ingredients_order(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::Ids>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_ingredients(&connection, &user, &ron.ids).await?;
|
||||
check_user_rights_recipe_ingredients(&connection, &context.user, &ron.ids).await?;
|
||||
connection.set_ingredients_order(&ron.ids).await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use axum::{
|
|||
use common::ron_api;
|
||||
|
||||
use crate::{
|
||||
Context,
|
||||
data::db,
|
||||
model,
|
||||
ron_extractor::ExtractRon,
|
||||
|
|
@ -33,9 +34,9 @@ impl From<model::ShoppingListItem> for common::ron_api::ShoppingListItem {
|
|||
#[debug_handler]
|
||||
pub async fn get(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
if let Some(user) = user {
|
||||
if let Some(user) = context.user {
|
||||
Ok(ron_response_ok(
|
||||
connection
|
||||
.get_shopping_list(user.id)
|
||||
|
|
@ -55,10 +56,10 @@ pub async fn get(
|
|||
#[debug_handler]
|
||||
pub async fn set_entry_checked(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::Value<bool>>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_shopping_list_entry(&connection, &user, ron.id).await?;
|
||||
check_user_rights_shopping_list_entry(&connection, &context.user, ron.id).await?;
|
||||
Ok(ron_response_ok(
|
||||
connection.set_entry_checked(ron.id, ron.value).await?,
|
||||
))
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use axum::{
|
|||
};
|
||||
use axum_extra::extract::{
|
||||
Host, Query,
|
||||
cookie::{Cookie, CookieJar},
|
||||
cookie::{self, Cookie, CookieJar},
|
||||
};
|
||||
use chrono::Duration;
|
||||
use lettre::Address;
|
||||
|
|
@ -19,14 +19,8 @@ use serde::Deserialize;
|
|||
use tracing::{Level, event};
|
||||
|
||||
use crate::{
|
||||
AppState, Result,
|
||||
config::Config,
|
||||
consts,
|
||||
data::{db, model},
|
||||
email,
|
||||
html_templates::*,
|
||||
translation::{self, Sentence},
|
||||
utils,
|
||||
AppState, Context, Result, config::Config, consts, data::db, email, html_templates::*,
|
||||
translation::Sentence, utils,
|
||||
};
|
||||
|
||||
/// SIGN UP ///
|
||||
|
|
@ -34,14 +28,12 @@ use crate::{
|
|||
#[debug_handler]
|
||||
pub async fn sign_up_get(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
) -> Result<Response> {
|
||||
if connection.get_new_user_registration_enabled().await? {
|
||||
Ok(Html(
|
||||
SignUpFormTemplate {
|
||||
user,
|
||||
tr,
|
||||
context,
|
||||
email: String::new(),
|
||||
message: "",
|
||||
message_email: "",
|
||||
|
|
@ -51,10 +43,15 @@ pub async fn sign_up_get(
|
|||
)
|
||||
.into_response())
|
||||
} else {
|
||||
Ok(
|
||||
Html(MessageTemplate::new_with_user(tr.t(Sentence::SignUpClosed), tr, user).render()?)
|
||||
.into_response(),
|
||||
Ok(Html(
|
||||
MessageTemplate::new_with_user(
|
||||
context.tr.t(Sentence::SignUpClosed),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,40 +76,37 @@ pub async fn sign_up_post(
|
|||
Host(host): Host,
|
||||
State(connection): State<db::Connection>,
|
||||
State(config): State<Config>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
Form(form_data): Form<SignUpFormData>,
|
||||
) -> Result<Response> {
|
||||
fn error_response(
|
||||
error: SignUpError,
|
||||
form_data: &SignUpFormData,
|
||||
user: Option<model::User>,
|
||||
tr: translation::Tr,
|
||||
context: Context,
|
||||
) -> Result<Response> {
|
||||
let invalid_password_mess = &tr.tp(
|
||||
let invalid_password_mess = &context.tr.tp(
|
||||
Sentence::InvalidPassword,
|
||||
&[Box::new(common::consts::MIN_PASSWORD_SIZE)],
|
||||
);
|
||||
Ok(Html(
|
||||
SignUpFormTemplate {
|
||||
user,
|
||||
email: form_data.email.clone(),
|
||||
message_email: match error {
|
||||
SignUpError::InvalidEmail => tr.t(Sentence::InvalidEmail),
|
||||
SignUpError::InvalidEmail => context.tr.t(Sentence::InvalidEmail),
|
||||
_ => "",
|
||||
},
|
||||
message_password: match error {
|
||||
SignUpError::PasswordsNotEqual => tr.t(Sentence::PasswordDontMatch),
|
||||
SignUpError::PasswordsNotEqual => context.tr.t(Sentence::PasswordDontMatch),
|
||||
SignUpError::InvalidPassword => invalid_password_mess,
|
||||
_ => "",
|
||||
},
|
||||
message: match error {
|
||||
SignUpError::UserAlreadyExists => tr.t(Sentence::EmailAlreadyTaken),
|
||||
SignUpError::DatabaseError => tr.t(Sentence::DatabaseError),
|
||||
SignUpError::UnableSendEmail => tr.t(Sentence::UnableToSendEmail),
|
||||
SignUpError::UserAlreadyExists => context.tr.t(Sentence::EmailAlreadyTaken),
|
||||
SignUpError::DatabaseError => context.tr.t(Sentence::DatabaseError),
|
||||
SignUpError::UnableSendEmail => context.tr.t(Sentence::UnableToSendEmail),
|
||||
_ => "",
|
||||
},
|
||||
tr,
|
||||
context,
|
||||
}
|
||||
.render()?,
|
||||
)
|
||||
|
|
@ -121,24 +115,29 @@ pub async fn sign_up_post(
|
|||
|
||||
if !connection.get_new_user_registration_enabled().await? {
|
||||
return Ok(Html(
|
||||
MessageTemplate::new_with_user(tr.t(Sentence::SignUpClosed), tr, user).render()?,
|
||||
MessageTemplate::new_with_user(
|
||||
context.tr.t(Sentence::SignUpClosed),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
|
||||
// Validation of email and password.
|
||||
if form_data.email.parse::<Address>().is_err() {
|
||||
return error_response(SignUpError::InvalidEmail, &form_data, user, tr);
|
||||
return error_response(SignUpError::InvalidEmail, &form_data, context);
|
||||
}
|
||||
|
||||
if form_data.password_1 != form_data.password_2 {
|
||||
return error_response(SignUpError::PasswordsNotEqual, &form_data, user, tr);
|
||||
return error_response(SignUpError::PasswordsNotEqual, &form_data, context);
|
||||
}
|
||||
|
||||
if let common::utils::PasswordValidation::TooShort =
|
||||
common::utils::validate_password(&form_data.password_1)
|
||||
{
|
||||
return error_response(SignUpError::InvalidPassword, &form_data, user, tr);
|
||||
return error_response(SignUpError::InvalidPassword, &form_data, context);
|
||||
}
|
||||
|
||||
match connection
|
||||
|
|
@ -146,14 +145,14 @@ pub async fn sign_up_post(
|
|||
.await
|
||||
{
|
||||
Ok(db::user::SignUpResult::UserAlreadyExists) => {
|
||||
error_response(SignUpError::UserAlreadyExists, &form_data, user, tr)
|
||||
error_response(SignUpError::UserAlreadyExists, &form_data, context)
|
||||
}
|
||||
Ok(db::user::SignUpResult::UserCreatedWaitingForValidation(token)) => {
|
||||
let url = utils::get_url_from_host(&host);
|
||||
let email = form_data.email.clone();
|
||||
match email::send_email(
|
||||
&email,
|
||||
&tr.tp(
|
||||
&context.tr.tp(
|
||||
Sentence::SignUpFollowEmailLink,
|
||||
&[Box::new(format!(
|
||||
"{}/validation?validation_token={}",
|
||||
|
|
@ -167,19 +166,23 @@ pub async fn sign_up_post(
|
|||
.await
|
||||
{
|
||||
Ok(()) => Ok(Html(
|
||||
MessageTemplate::new_with_user(tr.t(Sentence::SignUpEmailSent), tr, user)
|
||||
.render()?,
|
||||
MessageTemplate::new_with_user(
|
||||
context.tr.t(Sentence::SignUpEmailSent),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
)
|
||||
.into_response()),
|
||||
Err(_) => {
|
||||
// error!("Email validation error: {}", error); // TODO: log
|
||||
error_response(SignUpError::UnableSendEmail, &form_data, user, tr)
|
||||
error_response(SignUpError::UnableSendEmail, &form_data, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// error!("Signup database error: {}", error); // TODO: log
|
||||
error_response(SignUpError::DatabaseError, &form_data, user, tr)
|
||||
error_response(SignUpError::DatabaseError, &form_data, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -187,21 +190,20 @@ pub async fn sign_up_post(
|
|||
#[debug_handler]
|
||||
pub async fn sign_up_validation(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
Query(query): Query<HashMap<String, String>>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<(CookieJar, impl IntoResponse)> {
|
||||
let mut jar = CookieJar::from_headers(&headers);
|
||||
if user.is_some() {
|
||||
if context.user.is_some() {
|
||||
return Ok((
|
||||
jar,
|
||||
Html(
|
||||
MessageTemplate::new_with_user(
|
||||
tr.t(Sentence::ValidationUserAlreadyExists),
|
||||
tr,
|
||||
user,
|
||||
context.tr.t(Sentence::ValidationUserAlreadyExists),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
),
|
||||
|
|
@ -221,15 +223,16 @@ pub async fn sign_up_validation(
|
|||
.await?
|
||||
{
|
||||
db::user::ValidationResult::Ok(token, user_id) => {
|
||||
let cookie = Cookie::new(consts::COOKIE_AUTH_TOKEN_NAME, token);
|
||||
let cookie = Cookie::build((consts::COOKIE_AUTH_TOKEN_NAME, token))
|
||||
.same_site(cookie::SameSite::Strict);
|
||||
jar = jar.add(cookie);
|
||||
let user = connection.load_user(user_id).await?;
|
||||
Ok((
|
||||
jar,
|
||||
Html(
|
||||
MessageTemplate::new_with_user(
|
||||
tr.t(Sentence::SignUpEmailValidationSuccess),
|
||||
tr,
|
||||
context.tr.t(Sentence::SignUpEmailValidationSuccess),
|
||||
context.tr,
|
||||
user,
|
||||
)
|
||||
.render()?,
|
||||
|
|
@ -240,9 +243,9 @@ pub async fn sign_up_validation(
|
|||
jar,
|
||||
Html(
|
||||
MessageTemplate::new_with_user(
|
||||
tr.t(Sentence::SignUpValidationExpired),
|
||||
tr,
|
||||
user,
|
||||
context.tr.t(Sentence::SignUpValidationExpired),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
),
|
||||
|
|
@ -251,9 +254,9 @@ pub async fn sign_up_validation(
|
|||
jar,
|
||||
Html(
|
||||
MessageTemplate::new_with_user(
|
||||
tr.t(Sentence::SignUpValidationErrorTryAgain),
|
||||
tr,
|
||||
user,
|
||||
context.tr.t(Sentence::SignUpValidationErrorTryAgain),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
),
|
||||
|
|
@ -263,8 +266,12 @@ pub async fn sign_up_validation(
|
|||
None => Ok((
|
||||
jar,
|
||||
Html(
|
||||
MessageTemplate::new_with_user(tr.t(Sentence::ValidationError), tr, user)
|
||||
.render()?,
|
||||
MessageTemplate::new_with_user(
|
||||
context.tr.t(Sentence::ValidationError),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
),
|
||||
)),
|
||||
}
|
||||
|
|
@ -273,14 +280,10 @@ pub async fn sign_up_validation(
|
|||
/// SIGN IN ///
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn sign_in_get(
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
pub async fn sign_in_get(Extension(context): Extension<Context>) -> Result<impl IntoResponse> {
|
||||
Ok(Html(
|
||||
SignInFormTemplate {
|
||||
user,
|
||||
tr,
|
||||
context,
|
||||
email: "",
|
||||
message: "",
|
||||
}
|
||||
|
|
@ -298,8 +301,7 @@ pub struct SignInFormData {
|
|||
pub async fn sign_in_post(
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
headers: HeaderMap,
|
||||
Form(form_data): Form<SignInFormData>,
|
||||
) -> Result<(CookieJar, Response)> {
|
||||
|
|
@ -319,10 +321,9 @@ pub async fn sign_in_post(
|
|||
jar,
|
||||
Html(
|
||||
SignInFormTemplate {
|
||||
user,
|
||||
email: &form_data.email,
|
||||
message: tr.t(Sentence::AccountMustBeValidatedFirst),
|
||||
tr,
|
||||
message: context.tr.t(Sentence::AccountMustBeValidatedFirst),
|
||||
context,
|
||||
}
|
||||
.render()?,
|
||||
)
|
||||
|
|
@ -332,20 +333,20 @@ pub async fn sign_in_post(
|
|||
jar,
|
||||
Html(
|
||||
SignInFormTemplate {
|
||||
user,
|
||||
email: &form_data.email,
|
||||
message: tr.t(Sentence::WrongEmailOrPassword),
|
||||
tr,
|
||||
message: context.tr.t(Sentence::WrongEmailOrPassword),
|
||||
context,
|
||||
}
|
||||
.render()?,
|
||||
)
|
||||
.into_response(),
|
||||
)),
|
||||
db::user::SignInResult::Ok(token, _user_id) => {
|
||||
let cookie = Cookie::new(consts::COOKIE_AUTH_TOKEN_NAME, token);
|
||||
let cookie = Cookie::build((consts::COOKIE_AUTH_TOKEN_NAME, token))
|
||||
.same_site(cookie::SameSite::Strict);
|
||||
Ok((
|
||||
jar.add(cookie),
|
||||
Redirect::to(&format!("/{}/", tr.current_lang_code())).into_response(),
|
||||
Redirect::to(&format!("/{}/", context.tr.current_lang_code())).into_response(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -356,7 +357,7 @@ pub async fn sign_in_post(
|
|||
#[debug_handler]
|
||||
pub async fn sign_out(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
req: Request<Body>,
|
||||
) -> Result<(CookieJar, Redirect)> {
|
||||
let mut jar = CookieJar::from_headers(req.headers());
|
||||
|
|
@ -365,27 +366,30 @@ pub async fn sign_out(
|
|||
jar = jar.remove(consts::COOKIE_AUTH_TOKEN_NAME);
|
||||
connection.sign_out(&token).await?;
|
||||
}
|
||||
Ok((jar, Redirect::to(&format!("/{}/", tr.current_lang_code()))))
|
||||
Ok((
|
||||
jar,
|
||||
Redirect::to(&format!("/{}/", context.tr.current_lang_code())),
|
||||
))
|
||||
}
|
||||
|
||||
/// RESET PASSWORD ///
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn ask_reset_password_get(
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
) -> Result<Response> {
|
||||
if user.is_some() {
|
||||
pub async fn ask_reset_password_get(Extension(context): Extension<Context>) -> Result<Response> {
|
||||
if context.user.is_some() {
|
||||
Ok(Html(
|
||||
MessageTemplate::new_with_user(tr.t(Sentence::AskResetAlreadyLoggedInError), tr, user)
|
||||
.render()?,
|
||||
MessageTemplate::new_with_user(
|
||||
context.tr.t(Sentence::AskResetAlreadyLoggedInError),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
)
|
||||
.into_response())
|
||||
} else {
|
||||
Ok(Html(
|
||||
AskResetPasswordTemplate {
|
||||
user,
|
||||
tr,
|
||||
context,
|
||||
email: "",
|
||||
message: "",
|
||||
message_email: "",
|
||||
|
|
@ -414,36 +418,33 @@ pub async fn ask_reset_password_post(
|
|||
Host(host): Host,
|
||||
State(connection): State<db::Connection>,
|
||||
State(config): State<Config>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
Form(form_data): Form<AskResetPasswordForm>,
|
||||
) -> Result<Response> {
|
||||
fn error_response(
|
||||
error: AskResetPasswordError,
|
||||
email: &str,
|
||||
user: Option<model::User>,
|
||||
tr: translation::Tr,
|
||||
context: Context,
|
||||
) -> Result<Response> {
|
||||
Ok(Html(
|
||||
AskResetPasswordTemplate {
|
||||
user,
|
||||
email,
|
||||
message_email: match error {
|
||||
AskResetPasswordError::InvalidEmail => tr.t(Sentence::InvalidEmail),
|
||||
AskResetPasswordError::InvalidEmail => context.tr.t(Sentence::InvalidEmail),
|
||||
_ => "",
|
||||
},
|
||||
message: match error {
|
||||
AskResetPasswordError::EmailAlreadyReset => {
|
||||
tr.t(Sentence::AskResetEmailAlreadyResetError)
|
||||
context.tr.t(Sentence::AskResetEmailAlreadyResetError)
|
||||
}
|
||||
AskResetPasswordError::EmailUnknown => tr.t(Sentence::EmailUnknown),
|
||||
AskResetPasswordError::EmailUnknown => context.tr.t(Sentence::EmailUnknown),
|
||||
AskResetPasswordError::UnableSendEmail => {
|
||||
tr.t(Sentence::UnableToSendResetEmail)
|
||||
context.tr.t(Sentence::UnableToSendResetEmail)
|
||||
}
|
||||
AskResetPasswordError::DatabaseError => tr.t(Sentence::DatabaseError),
|
||||
AskResetPasswordError::DatabaseError => context.tr.t(Sentence::DatabaseError),
|
||||
_ => "",
|
||||
},
|
||||
tr,
|
||||
context,
|
||||
}
|
||||
.render()?,
|
||||
)
|
||||
|
|
@ -455,8 +456,7 @@ pub async fn ask_reset_password_post(
|
|||
return error_response(
|
||||
AskResetPasswordError::InvalidEmail,
|
||||
&form_data.email,
|
||||
user,
|
||||
tr,
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -470,20 +470,18 @@ pub async fn ask_reset_password_post(
|
|||
Ok(db::user::GetTokenResetPasswordResult::PasswordAlreadyReset) => error_response(
|
||||
AskResetPasswordError::EmailAlreadyReset,
|
||||
&form_data.email,
|
||||
user,
|
||||
tr,
|
||||
context,
|
||||
),
|
||||
Ok(db::user::GetTokenResetPasswordResult::EmailUnknown) => error_response(
|
||||
AskResetPasswordError::EmailUnknown,
|
||||
&form_data.email,
|
||||
user,
|
||||
tr,
|
||||
context,
|
||||
),
|
||||
Ok(db::user::GetTokenResetPasswordResult::Ok(token)) => {
|
||||
let url = utils::get_url_from_host(&host);
|
||||
match email::send_email(
|
||||
&form_data.email,
|
||||
&tr.tp(
|
||||
&context.tr.tp(
|
||||
Sentence::AskResetFollowEmailLink,
|
||||
&[Box::new(format!(
|
||||
"{}/reset_password?reset_token={}",
|
||||
|
|
@ -497,8 +495,12 @@ pub async fn ask_reset_password_post(
|
|||
.await
|
||||
{
|
||||
Ok(()) => Ok(Html(
|
||||
MessageTemplate::new_with_user(tr.t(Sentence::AskResetEmailSent), tr, user)
|
||||
.render()?,
|
||||
MessageTemplate::new_with_user(
|
||||
context.tr.t(Sentence::AskResetEmailSent),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
)
|
||||
.into_response()),
|
||||
Err(_) => {
|
||||
|
|
@ -506,8 +508,7 @@ pub async fn ask_reset_password_post(
|
|||
error_response(
|
||||
AskResetPasswordError::UnableSendEmail,
|
||||
&form_data.email,
|
||||
user,
|
||||
tr,
|
||||
context,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -517,8 +518,7 @@ pub async fn ask_reset_password_post(
|
|||
error_response(
|
||||
AskResetPasswordError::DatabaseError,
|
||||
&form_data.email,
|
||||
user,
|
||||
tr,
|
||||
context,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -527,8 +527,7 @@ pub async fn ask_reset_password_post(
|
|||
#[debug_handler]
|
||||
pub async fn reset_password_get(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
Query(query): Query<HashMap<String, String>>,
|
||||
) -> Result<Response> {
|
||||
if let Some(reset_token) = query.get("reset_token") {
|
||||
|
|
@ -542,8 +541,7 @@ pub async fn reset_password_get(
|
|||
{
|
||||
Ok(Html(
|
||||
ResetPasswordTemplate {
|
||||
user,
|
||||
tr,
|
||||
context,
|
||||
reset_token,
|
||||
message: "",
|
||||
message_password: "",
|
||||
|
|
@ -553,15 +551,23 @@ pub async fn reset_password_get(
|
|||
.into_response())
|
||||
} else {
|
||||
Ok(Html(
|
||||
MessageTemplate::new_with_user(tr.t(Sentence::AskResetTokenMissing), tr, user)
|
||||
.render()?,
|
||||
MessageTemplate::new_with_user(
|
||||
context.tr.t(Sentence::AskResetTokenMissing),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
} else {
|
||||
Ok(Html(
|
||||
MessageTemplate::new_with_user(tr.t(Sentence::AskResetTokenMissing), tr, user)
|
||||
.render()?,
|
||||
MessageTemplate::new_with_user(
|
||||
context.tr.t(Sentence::AskResetTokenMissing),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
|
|
@ -584,35 +590,36 @@ enum ResetPasswordError {
|
|||
#[debug_handler]
|
||||
pub async fn reset_password_post(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
Form(form_data): Form<ResetPasswordForm>,
|
||||
) -> Result<Response> {
|
||||
fn error_response(
|
||||
error: ResetPasswordError,
|
||||
form_data: &ResetPasswordForm,
|
||||
user: Option<model::User>,
|
||||
tr: translation::Tr,
|
||||
context: Context,
|
||||
) -> Result<Response> {
|
||||
let reset_password_mess = &tr.tp(
|
||||
let reset_password_mess = &context.tr.tp(
|
||||
Sentence::InvalidPassword,
|
||||
&[Box::new(common::consts::MIN_PASSWORD_SIZE)],
|
||||
);
|
||||
Ok(Html(
|
||||
ResetPasswordTemplate {
|
||||
user,
|
||||
reset_token: &form_data.reset_token,
|
||||
message_password: match error {
|
||||
ResetPasswordError::PasswordsNotEqual => tr.t(Sentence::PasswordDontMatch),
|
||||
ResetPasswordError::PasswordsNotEqual => {
|
||||
context.tr.t(Sentence::PasswordDontMatch)
|
||||
}
|
||||
ResetPasswordError::InvalidPassword => reset_password_mess,
|
||||
_ => "",
|
||||
},
|
||||
message: match error {
|
||||
ResetPasswordError::TokenExpired => tr.t(Sentence::AskResetTokenExpired),
|
||||
ResetPasswordError::DatabaseError => tr.t(Sentence::DatabaseError),
|
||||
ResetPasswordError::TokenExpired => {
|
||||
context.tr.t(Sentence::AskResetTokenExpired)
|
||||
}
|
||||
ResetPasswordError::DatabaseError => context.tr.t(Sentence::DatabaseError),
|
||||
_ => "",
|
||||
},
|
||||
tr,
|
||||
context,
|
||||
}
|
||||
.render()?,
|
||||
)
|
||||
|
|
@ -620,13 +627,13 @@ pub async fn reset_password_post(
|
|||
}
|
||||
|
||||
if form_data.password_1 != form_data.password_2 {
|
||||
return error_response(ResetPasswordError::PasswordsNotEqual, &form_data, user, tr);
|
||||
return error_response(ResetPasswordError::PasswordsNotEqual, &form_data, context);
|
||||
}
|
||||
|
||||
if let common::utils::PasswordValidation::TooShort =
|
||||
common::utils::validate_password(&form_data.password_1)
|
||||
{
|
||||
return error_response(ResetPasswordError::InvalidPassword, &form_data, user, tr);
|
||||
return error_response(ResetPasswordError::InvalidPassword, &form_data, context);
|
||||
}
|
||||
|
||||
match connection
|
||||
|
|
@ -638,24 +645,26 @@ pub async fn reset_password_post(
|
|||
.await
|
||||
{
|
||||
Ok(db::user::ResetPasswordResult::Ok) => Ok(Html(
|
||||
MessageTemplate::new_with_user(tr.t(Sentence::PasswordReset), tr, user).render()?,
|
||||
MessageTemplate::new_with_user(
|
||||
context.tr.t(Sentence::PasswordReset),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
)
|
||||
.into_response()),
|
||||
Ok(db::user::ResetPasswordResult::ResetTokenExpired) => {
|
||||
error_response(ResetPasswordError::TokenExpired, &form_data, user, tr)
|
||||
error_response(ResetPasswordError::TokenExpired, &form_data, context)
|
||||
}
|
||||
Err(_) => error_response(ResetPasswordError::DatabaseError, &form_data, user, tr),
|
||||
Err(_) => error_response(ResetPasswordError::DatabaseError, &form_data, context),
|
||||
}
|
||||
}
|
||||
|
||||
/// EDIT PROFILE ///
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn edit_user_get(
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
) -> Result<Response> {
|
||||
Ok(if let Some(user) = user {
|
||||
pub async fn edit_user_get(Extension(context): Extension<Context>) -> Result<Response> {
|
||||
Ok(if let Some(ref user) = context.user {
|
||||
Html(
|
||||
ProfileTemplate {
|
||||
username: &user.name,
|
||||
|
|
@ -664,14 +673,14 @@ pub async fn edit_user_get(
|
|||
message: "",
|
||||
message_email: "",
|
||||
message_password: "",
|
||||
user: Some(user.clone()),
|
||||
tr,
|
||||
context: context.clone(),
|
||||
}
|
||||
.render()?,
|
||||
)
|
||||
.into_response()
|
||||
} else {
|
||||
Html(MessageTemplate::new(tr.t(Sentence::NotLoggedIn), tr).render()?).into_response()
|
||||
Html(MessageTemplate::new(context.tr.t(Sentence::NotLoggedIn), context.tr).render()?)
|
||||
.into_response()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -699,43 +708,46 @@ pub async fn edit_user_post(
|
|||
Host(host): Host,
|
||||
State(connection): State<db::Connection>,
|
||||
State(config): State<Config>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
Form(form_data): Form<EditUserForm>,
|
||||
) -> Result<Response> {
|
||||
if let Some(user) = user {
|
||||
if let Some(ref user) = context.user {
|
||||
fn error_response(
|
||||
error: ProfileUpdateError,
|
||||
form_data: &EditUserForm,
|
||||
user: model::User,
|
||||
tr: translation::Tr,
|
||||
context: Context,
|
||||
) -> Result<Response> {
|
||||
let invalid_password_mess = &tr.tp(
|
||||
let invalid_password_mess = &context.tr.tp(
|
||||
Sentence::InvalidPassword,
|
||||
&[Box::new(common::consts::MIN_PASSWORD_SIZE)],
|
||||
);
|
||||
Ok(Html(
|
||||
ProfileTemplate {
|
||||
user: Some(user),
|
||||
username: &form_data.name,
|
||||
email: &form_data.email,
|
||||
default_servings: form_data.default_servings,
|
||||
message_email: match error {
|
||||
ProfileUpdateError::InvalidEmail => tr.t(Sentence::InvalidEmail),
|
||||
ProfileUpdateError::EmailAlreadyTaken => tr.t(Sentence::EmailAlreadyTaken),
|
||||
ProfileUpdateError::InvalidEmail => context.tr.t(Sentence::InvalidEmail),
|
||||
ProfileUpdateError::EmailAlreadyTaken => {
|
||||
context.tr.t(Sentence::EmailAlreadyTaken)
|
||||
}
|
||||
_ => "",
|
||||
},
|
||||
message_password: match error {
|
||||
ProfileUpdateError::PasswordsNotEqual => tr.t(Sentence::PasswordDontMatch),
|
||||
ProfileUpdateError::PasswordsNotEqual => {
|
||||
context.tr.t(Sentence::PasswordDontMatch)
|
||||
}
|
||||
ProfileUpdateError::InvalidPassword => invalid_password_mess,
|
||||
_ => "",
|
||||
},
|
||||
message: match error {
|
||||
ProfileUpdateError::DatabaseError => tr.t(Sentence::DatabaseError),
|
||||
ProfileUpdateError::UnableSendEmail => tr.t(Sentence::UnableToSendEmail),
|
||||
ProfileUpdateError::DatabaseError => context.tr.t(Sentence::DatabaseError),
|
||||
ProfileUpdateError::UnableSendEmail => {
|
||||
context.tr.t(Sentence::UnableToSendEmail)
|
||||
}
|
||||
_ => "",
|
||||
},
|
||||
tr,
|
||||
context,
|
||||
}
|
||||
.render()?,
|
||||
)
|
||||
|
|
@ -743,17 +755,17 @@ pub async fn edit_user_post(
|
|||
}
|
||||
|
||||
if form_data.email.parse::<Address>().is_err() {
|
||||
return error_response(ProfileUpdateError::InvalidEmail, &form_data, user, tr);
|
||||
return error_response(ProfileUpdateError::InvalidEmail, &form_data, context);
|
||||
}
|
||||
|
||||
let new_password = if !form_data.password_1.is_empty() || !form_data.password_2.is_empty() {
|
||||
if form_data.password_1 != form_data.password_2 {
|
||||
return error_response(ProfileUpdateError::PasswordsNotEqual, &form_data, user, tr);
|
||||
return error_response(ProfileUpdateError::PasswordsNotEqual, &form_data, context);
|
||||
}
|
||||
if let common::utils::PasswordValidation::TooShort =
|
||||
common::utils::validate_password(&form_data.password_1)
|
||||
{
|
||||
return error_response(ProfileUpdateError::InvalidPassword, &form_data, user, tr);
|
||||
return error_response(ProfileUpdateError::InvalidPassword, &form_data, context);
|
||||
}
|
||||
Some(form_data.password_1.as_ref())
|
||||
} else {
|
||||
|
|
@ -774,14 +786,14 @@ pub async fn edit_user_post(
|
|||
.await
|
||||
{
|
||||
Ok(db::user::UpdateUserResult::EmailAlreadyTaken) => {
|
||||
return error_response(ProfileUpdateError::EmailAlreadyTaken, &form_data, user, tr);
|
||||
return error_response(ProfileUpdateError::EmailAlreadyTaken, &form_data, context);
|
||||
}
|
||||
Ok(db::user::UpdateUserResult::UserUpdatedWaitingForRevalidation(token)) => {
|
||||
let url = utils::get_url_from_host(&host);
|
||||
let email = form_data.email.clone();
|
||||
match email::send_email(
|
||||
&email,
|
||||
&tr.tp(
|
||||
&context.tr.tp(
|
||||
Sentence::ProfileFollowEmailLink,
|
||||
&[Box::new(format!(
|
||||
"{}/revalidation?validation_token={}",
|
||||
|
|
@ -795,24 +807,23 @@ pub async fn edit_user_post(
|
|||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
message = tr.t(Sentence::ProfileEmailSent);
|
||||
message = context.tr.t(Sentence::ProfileEmailSent);
|
||||
}
|
||||
Err(_) => {
|
||||
// error!("Email validation error: {}", error); // TODO: log
|
||||
return error_response(
|
||||
ProfileUpdateError::UnableSendEmail,
|
||||
&form_data,
|
||||
user,
|
||||
tr,
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(db::user::UpdateUserResult::Ok) => {
|
||||
message = tr.t(Sentence::ProfileSaved);
|
||||
message = context.tr.t(Sentence::ProfileSaved);
|
||||
}
|
||||
Err(_) => {
|
||||
return error_response(ProfileUpdateError::DatabaseError, &form_data, user, tr);
|
||||
return error_response(ProfileUpdateError::DatabaseError, &form_data, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -821,41 +832,42 @@ pub async fn edit_user_post(
|
|||
|
||||
Ok(Html(
|
||||
ProfileTemplate {
|
||||
user,
|
||||
username: &form_data.name,
|
||||
email: &form_data.email,
|
||||
default_servings: form_data.default_servings,
|
||||
message,
|
||||
message_email: "",
|
||||
message_password: "",
|
||||
tr,
|
||||
context: Context { user, ..context },
|
||||
}
|
||||
.render()?,
|
||||
)
|
||||
.into_response())
|
||||
} else {
|
||||
Ok(Html(MessageTemplate::new(tr.t(Sentence::NotLoggedIn), tr).render()?).into_response())
|
||||
Ok(
|
||||
Html(MessageTemplate::new(context.tr.t(Sentence::NotLoggedIn), context.tr).render()?)
|
||||
.into_response(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn email_revalidation(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
Extension(tr): Extension<translation::Tr>,
|
||||
Extension(context): Extension<Context>,
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
Query(query): Query<HashMap<String, String>>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<(CookieJar, impl IntoResponse)> {
|
||||
let mut jar = CookieJar::from_headers(&headers);
|
||||
if user.is_some() {
|
||||
if context.user.is_some() {
|
||||
return Ok((
|
||||
jar,
|
||||
Html(
|
||||
MessageTemplate::new_with_user(
|
||||
tr.t(Sentence::ValidationUserAlreadyExists),
|
||||
tr,
|
||||
user,
|
||||
context.tr.t(Sentence::ValidationUserAlreadyExists),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
),
|
||||
|
|
@ -875,15 +887,16 @@ pub async fn email_revalidation(
|
|||
.await?
|
||||
{
|
||||
db::user::ValidationResult::Ok(token, user_id) => {
|
||||
let cookie = Cookie::new(consts::COOKIE_AUTH_TOKEN_NAME, token);
|
||||
let cookie = Cookie::build((consts::COOKIE_AUTH_TOKEN_NAME, token))
|
||||
.same_site(cookie::SameSite::Strict);
|
||||
jar = jar.add(cookie);
|
||||
let user = connection.load_user(user_id).await?;
|
||||
Ok((
|
||||
jar,
|
||||
Html(
|
||||
MessageTemplate::new_with_user(
|
||||
tr.t(Sentence::ValidationSuccessful),
|
||||
tr,
|
||||
context.tr.t(Sentence::ValidationSuccessful),
|
||||
context.tr,
|
||||
user,
|
||||
)
|
||||
.render()?,
|
||||
|
|
@ -893,17 +906,21 @@ pub async fn email_revalidation(
|
|||
db::user::ValidationResult::ValidationExpired => Ok((
|
||||
jar,
|
||||
Html(
|
||||
MessageTemplate::new_with_user(tr.t(Sentence::ValidationExpired), tr, user)
|
||||
.render()?,
|
||||
MessageTemplate::new_with_user(
|
||||
context.tr.t(Sentence::ValidationExpired),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
),
|
||||
)),
|
||||
db::user::ValidationResult::UnknownUser => Ok((
|
||||
jar,
|
||||
Html(
|
||||
MessageTemplate::new_with_user(
|
||||
tr.t(Sentence::ValidationErrorTryToSignUpAgain),
|
||||
tr,
|
||||
user,
|
||||
context.tr.t(Sentence::ValidationErrorTryToSignUpAgain),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
),
|
||||
|
|
@ -913,8 +930,12 @@ pub async fn email_revalidation(
|
|||
None => Ok((
|
||||
jar,
|
||||
Html(
|
||||
MessageTemplate::new_with_user(tr.t(Sentence::ValidationError), tr, user)
|
||||
.render()?,
|
||||
MessageTemplate::new_with_user(
|
||||
context.tr.t(Sentence::ValidationError),
|
||||
context.tr,
|
||||
context.user,
|
||||
)
|
||||
.render()?,
|
||||
),
|
||||
)),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ pub enum Sentence {
|
|||
|
||||
// Reset password page.
|
||||
LostPassword,
|
||||
AskResetChooseNewPassword,
|
||||
AskResetButton,
|
||||
AskResetAlreadyLoggedInError,
|
||||
AskResetEmailAlreadyResetError,
|
||||
|
|
@ -153,7 +154,7 @@ pub enum Sentence {
|
|||
pub const DEFAULT_LANGUAGE_CODE: &str = "en";
|
||||
pub const PLACEHOLDER_SUBSTITUTE: &str = "{}";
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Tr {
|
||||
lang: &'static Language,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
{% block main_container %}
|
||||
<div class="content" id="ask-reset-password">
|
||||
<h1>{{ tr.t(Sentence::LostPassword) }}</h1>
|
||||
<h1>{{ context.tr.t(Sentence::LostPassword) }}</h1>
|
||||
<form action="/ask_reset_password" method="post">
|
||||
<label for="email_field">{{ tr.t(Sentence::EmailAddress) }}</label>
|
||||
<label for="email_field">{{ context.tr.t(Sentence::EmailAddress) }}</label>
|
||||
<input id="email_field" type="email"
|
||||
name="email" value="{{ email }}"
|
||||
autocapitalize="none" autocomplete="email" autofocus="autofocus">
|
||||
<span class="user-message">{{ message_email }}</span>
|
||||
|
||||
<input type="submit" name="commit" value="{{ tr.t(Sentence::AskResetButton) }}">
|
||||
<input type="submit" name="commit" value="{{ context.tr.t(Sentence::AskResetButton) }}">
|
||||
</form>
|
||||
|
||||
<span class="user-message">{{ message }}</span>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ tr.current_lang_and_territory_code() }}" data-user-logged="{{ user.is_some() }}" >
|
||||
<html lang="{{ context.tr.current_lang_and_territory_code() }}" data-user-logged="{{ context.user.is_some() }}" >
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Recettes de cuisine</title>
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/
|
||||
{% if context.dark_theme %}
|
||||
style_dark.css
|
||||
{% else %}
|
||||
style_light.css
|
||||
{% endif %}">
|
||||
<link rel="modulepreload" href="/static/wasm/frontend.js" crossorigin="anonymous" as="fetch" type="application/wasm">
|
||||
</head>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,29 +5,37 @@
|
|||
{% include "title.html" %}
|
||||
|
||||
<span class="header-menu">
|
||||
{% match user %}
|
||||
{% match context.user %}
|
||||
{% when Some with (user) %}
|
||||
<a class="create-recipe" href="/recipe/new" >{{ tr.t(Sentence::CreateNewRecipe) }}</a>
|
||||
<a href="/{{ tr.current_lang_code() }}/user/edit">
|
||||
<a class="create-recipe" href="/recipe/new" >{{ context.tr.t(Sentence::CreateNewRecipe) }}</a>
|
||||
<a href="/{{ context.tr.current_lang_code() }}/user/edit">
|
||||
{% if user.name == "" %}
|
||||
{{ user.email }}
|
||||
{% else %}
|
||||
{{ user.name }}
|
||||
{% endif %}
|
||||
</a> / <a href="/signout">{{ tr.t(Sentence::SignOut) }}</a>
|
||||
</a> / <a href="/signout">{{ context.tr.t(Sentence::SignOut) }}</a>
|
||||
{% when None %}
|
||||
<a href="/{{ tr.current_lang_code() }}/signin" >{{ tr.t(Sentence::SignInMenu) }}</a>/<a href="/{{ tr.current_lang_code() }}/signup">{{ tr.t(Sentence::SignUpMenu) }}</a>/<a href="/{{ tr.current_lang_code() }}/ask_reset_password">{{ tr.t(Sentence::LostPassword) }}</a>
|
||||
<a href="/{{ context.tr.current_lang_code() }}/signin" >{{ context.tr.t(Sentence::SignInMenu) }}</a>/<a href="/{{ context.tr.current_lang_code() }}/signup">{{ context.tr.t(Sentence::SignUpMenu) }}</a>/<a href="/{{ context.tr.current_lang_code() }}/ask_reset_password">{{ context.tr.t(Sentence::LostPassword) }}</a>
|
||||
{% endmatch %}
|
||||
|
||||
<select id="select-website-language">
|
||||
{% for lang in translation::available_languages() %}
|
||||
<option value="{{ lang.0 }}"
|
||||
{%~ if tr.current_lang_code() == lang.0 %}
|
||||
{%~ if context.tr.current_lang_code() == lang.0 %}
|
||||
selected
|
||||
{% endif %}
|
||||
>{{ lang.1 }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label id="toggle-theme">
|
||||
<input type="checkbox"
|
||||
{%~ if !context.dark_theme %}
|
||||
checked
|
||||
{% endif %} >
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
Sentence::CalendarNovember,
|
||||
Sentence::CalendarDecember,
|
||||
] %}
|
||||
<span class="month">{{ tr.t(*month) }}</span>
|
||||
<span class="month">{{ context.tr.t(*month) }}</span>
|
||||
{% endfor %}
|
||||
|
||||
<span class="next">NEXT</span>
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
Sentence::CalendarSaturdayAbbreviation,
|
||||
Sentence::CalendarSundayAbbreviation,
|
||||
] %}
|
||||
<li class="weekday">{{ tr.t(*day) }}</li>
|
||||
<li class="weekday">{{ context.tr.t(*day) }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
|
|
@ -52,17 +52,17 @@
|
|||
<div class="scheduled-recipe"></div>
|
||||
|
||||
<div class="unschedule-confirmation">
|
||||
<div>{{ tr.t(Sentence::CalendarUnscheduleConfirmation) }}</div>
|
||||
<div>{{ context.tr.t(Sentence::CalendarUnscheduleConfirmation) }}</div>
|
||||
<input
|
||||
id="input-remove-ingredients-from-shopping-list"
|
||||
type="checkbox"
|
||||
checked
|
||||
>
|
||||
<label for="input-remove-ingredients-from-shopping-list">
|
||||
{{ tr.t(Sentence::CalendarRemoveIngredientsFromShoppingList) }}
|
||||
{{ context.tr.t(Sentence::CalendarRemoveIngredientsFromShoppingList) }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<span class="calendar-date-format">{{ tr.t(Sentence::CalendarDateFormat) }}</span>
|
||||
<span class="calendar-date-format">{{ context.tr.t(Sentence::CalendarDateFormat) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
<div class="item-delete"></div>
|
||||
</div>
|
||||
|
||||
<span class="calendar-date-format">{{ tr.t(Sentence::CalendarDateFormat) }}</span>
|
||||
<span class="calendar-date-format">{{ context.tr.t(Sentence::CalendarDateFormat) }}</span>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
{% block main_container %}
|
||||
|
||||
{% if let Some(user) = user %}
|
||||
{% if let Some(user) = context.user %}
|
||||
|
||||
<div class="content" id="user-edit">
|
||||
<h1>{{ tr.t(Sentence::ProfileTitle) }}</h1>
|
||||
<h1>{{ context.tr.t(Sentence::ProfileTitle) }}</h1>
|
||||
|
||||
<form action="/{{ tr.current_lang_code() }}/user/edit" method="post">
|
||||
<form action="/{{ context.tr.current_lang_code() }}/user/edit" method="post">
|
||||
|
||||
<label for="input-name">{{ tr.t(Sentence::Name) }}</label>
|
||||
<label for="input-name">{{ context.tr.t(Sentence::Name) }}</label>
|
||||
<input
|
||||
id="input-name"
|
||||
type="text"
|
||||
|
|
@ -20,13 +20,13 @@
|
|||
autofocus="autofocus">
|
||||
<span></span>
|
||||
|
||||
<label for="input-email">{{ tr.t(Sentence::ProfileEmail) }}</label>
|
||||
<label for="input-email">{{ context.tr.t(Sentence::ProfileEmail) }}</label>
|
||||
<input id="input-email" type="email"
|
||||
name="email" value="{{ email }}"
|
||||
autocapitalize="none" autocomplete="email" autofocus="autofocus">
|
||||
<span class="user-message">{{ message_email }}</span>
|
||||
|
||||
<label for="input-servings">{{ tr.t(Sentence::ProfileDefaultServings) }}</label>
|
||||
<label for="input-servings">{{ context.tr.t(Sentence::ProfileDefaultServings) }}</label>
|
||||
<input
|
||||
id="input-servings"
|
||||
type="number"
|
||||
|
|
@ -35,15 +35,15 @@
|
|||
value="{{ default_servings }}">
|
||||
<span></span>
|
||||
|
||||
<label for="input-password-1">{{ tr.tp(Sentence::ProfileNewPassword, [Box::new(common::consts::MIN_PASSWORD_SIZE)]) }}</label>
|
||||
<label for="input-password-1">{{ context.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">
|
||||
<span></span>
|
||||
|
||||
<label for="input-password-2">{{ tr.t(Sentence::ReEnterPassword) }}</label>
|
||||
<label for="input-password-2">{{ context.tr.t(Sentence::ReEnterPassword) }}</label>
|
||||
<input id="input-password-2" type="password" name="password_2" autocomplete="new-password">
|
||||
<span class="user-message">{{ message_password }}</span>
|
||||
|
||||
<input type="submit" name="commit" value="{{ tr.t(Sentence::Save) }}">
|
||||
<input type="submit" name="commit" value="{{ context.tr.t(Sentence::Save) }}">
|
||||
</form>
|
||||
|
||||
<span class="user-message">{{ message }}</span>
|
||||
|
|
|
|||
|
|
@ -9,18 +9,18 @@
|
|||
{% block content %}
|
||||
|
||||
<div class="content" id="recipe-edit">
|
||||
<label for="input-title">{{ tr.t(Sentence::RecipeTitle) }}</label>
|
||||
<label for="input-title">{{ context.tr.t(Sentence::RecipeTitle) }}</label>
|
||||
<input
|
||||
id="input-title"
|
||||
type="text"
|
||||
value="{{ recipe.title }}"
|
||||
autofocus="true">
|
||||
|
||||
<label for="text-area-description">{{ tr.t(Sentence::RecipeDescription) }}</label>
|
||||
<label for="text-area-description">{{ context.tr.t(Sentence::RecipeDescription) }}</label>
|
||||
<textarea
|
||||
id="text-area-description">{{ recipe.description }}</textarea>
|
||||
|
||||
<label for="input-servings">{{ tr.t(Sentence::RecipeServings) }}</label>
|
||||
<label for="input-servings">{{ context.tr.t(Sentence::RecipeServings) }}</label>
|
||||
<input
|
||||
id="input-servings"
|
||||
type="number"
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
{{ s }}
|
||||
{% endif %}">
|
||||
|
||||
<label for="input-estimated-time">{{ tr.t(Sentence::RecipeEstimatedTime) }}</label>
|
||||
<label for="input-estimated-time">{{ context.tr.t(Sentence::RecipeEstimatedTime) }}</label>
|
||||
<input
|
||||
id="input-estimated-time"
|
||||
type="number"
|
||||
|
|
@ -40,16 +40,16 @@
|
|||
{{ t }}
|
||||
{% endif %}">
|
||||
|
||||
<label for="select-difficulty">{{ tr.t(Sentence::RecipeDifficulty) }}</label>
|
||||
<label for="select-difficulty">{{ context.tr.t(Sentence::RecipeDifficulty) }}</label>
|
||||
<select id="select-difficulty">
|
||||
<option value="0" {%~ call is_difficulty(common::ron_api::Difficulty::Unknown) %}> - </option>
|
||||
<option value="1" {%~ call is_difficulty(common::ron_api::Difficulty::Easy) %}>{{ tr.t(Sentence::RecipeDifficultyEasy) }}</option>
|
||||
<option value="2" {%~ call is_difficulty(common::ron_api::Difficulty::Medium) %}>{{ tr.t(Sentence::RecipeDifficultyMedium) }}</option>
|
||||
<option value="3" {%~ call is_difficulty(common::ron_api::Difficulty::Hard) %}>{{ tr.t(Sentence::RecipeDifficultyHard) }}</option>
|
||||
<option value="1" {%~ call is_difficulty(common::ron_api::Difficulty::Easy) %}>{{ context.tr.t(Sentence::RecipeDifficultyEasy) }}</option>
|
||||
<option value="2" {%~ call is_difficulty(common::ron_api::Difficulty::Medium) %}>{{ context.tr.t(Sentence::RecipeDifficultyMedium) }}</option>
|
||||
<option value="3" {%~ call is_difficulty(common::ron_api::Difficulty::Hard) %}>{{ context.tr.t(Sentence::RecipeDifficultyHard) }}</option>
|
||||
</select>
|
||||
|
||||
<div id="container-tags">
|
||||
<label for="input-tags" >{{ tr.t(Sentence::RecipeTags) }}</label>
|
||||
<label for="input-tags" >{{ context.tr.t(Sentence::RecipeTags) }}</label>
|
||||
<span class="tags"></span>
|
||||
<input
|
||||
id="input-tags"
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
value="">
|
||||
</div>
|
||||
|
||||
<label for="select-language">{{ tr.t(Sentence::RecipeLanguage) }}</label>
|
||||
<label for="select-language">{{ context.tr.t(Sentence::RecipeLanguage) }}</label>
|
||||
<select id="select-language">
|
||||
{% for lang in translation::available_languages() %}
|
||||
<option value="{{ lang.0 }}"
|
||||
|
|
@ -75,71 +75,71 @@
|
|||
checked
|
||||
{% endif %}
|
||||
>
|
||||
<label for="input-is-published">{{ tr.t(Sentence::RecipeIsPublished) }}</label>
|
||||
<label for="input-is-published">{{ context.tr.t(Sentence::RecipeIsPublished) }}</label>
|
||||
|
||||
<input id="input-delete" type="button" value="{{ tr.t(Sentence::RecipeDelete) }}">
|
||||
<input id="input-delete" type="button" value="{{ context.tr.t(Sentence::RecipeDelete) }}">
|
||||
|
||||
<div id="groups-container">
|
||||
</div>
|
||||
|
||||
<input id="input-add-group" type="button" value="{{ tr.t(Sentence::RecipeAddAGroup) }}">
|
||||
<input id="input-add-group" type="button" value="{{ context.tr.t(Sentence::RecipeAddAGroup) }}">
|
||||
</div>
|
||||
|
||||
<div id="hidden-templates">
|
||||
<div class="group">
|
||||
<span class="drag-handle"></span>
|
||||
|
||||
<label for="input-group-name">{{ tr.t(Sentence::RecipeGroupName) }}</label>
|
||||
<label for="input-group-name">{{ context.tr.t(Sentence::RecipeGroupName) }}</label>
|
||||
<input class="input-group-name" type="text">
|
||||
|
||||
<label for="input-group-comment">{{ tr.t(Sentence::RecipeGroupComment) }}</label>
|
||||
<label for="input-group-comment">{{ context.tr.t(Sentence::RecipeGroupComment) }}</label>
|
||||
<input class="input-group-comment" type="text">
|
||||
|
||||
<input class="input-group-delete" type="button" value="{{ tr.t(Sentence::RecipeRemoveGroup) }}">
|
||||
<input class="input-group-delete" type="button" value="{{ context.tr.t(Sentence::RecipeRemoveGroup) }}">
|
||||
|
||||
<div class="steps">
|
||||
</div>
|
||||
|
||||
<input class="input-add-step" type="button" value="{{ tr.t(Sentence::RecipeAddAStep) }}">
|
||||
<input class="input-add-step" type="button" value="{{ context.tr.t(Sentence::RecipeAddAStep) }}">
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<span class="drag-handle"></span>
|
||||
|
||||
<label for="text-area-step-action">{{ tr.t(Sentence::RecipeStepAction) }}</label>
|
||||
<label for="text-area-step-action">{{ context.tr.t(Sentence::RecipeStepAction) }}</label>
|
||||
<textarea class="text-area-step-action"></textarea>
|
||||
|
||||
<input class="input-step-delete" type="button" value="{{ tr.t(Sentence::RecipeRemoveStep) }}">
|
||||
<input class="input-step-delete" type="button" value="{{ context.tr.t(Sentence::RecipeRemoveStep) }}">
|
||||
|
||||
<div class="ingredients"></div>
|
||||
|
||||
<input class="input-add-ingredient" type="button" value="{{ tr.t(Sentence::RecipeAddAnIngredient) }}">
|
||||
<input class="input-add-ingredient" type="button" value="{{ context.tr.t(Sentence::RecipeAddAnIngredient) }}">
|
||||
</div>
|
||||
|
||||
<div class="ingredient">
|
||||
<span class="drag-handle"></span>
|
||||
|
||||
<label for="input-ingredient-name">{{ tr.t(Sentence::RecipeIngredientName) }}</label>
|
||||
<label for="input-ingredient-name">{{ context.tr.t(Sentence::RecipeIngredientName) }}</label>
|
||||
<input class="input-ingredient-name" type="text">
|
||||
|
||||
<label for="input-ingredient-quantity">{{ tr.t(Sentence::RecipeIngredientQuantity) }}</label>
|
||||
<label for="input-ingredient-quantity">{{ context.tr.t(Sentence::RecipeIngredientQuantity) }}</label>
|
||||
<input class="input-ingredient-quantity" type="number" step="0.1" min="0" max="10000">
|
||||
|
||||
<label for="input-ingredient-unit">{{ tr.t(Sentence::RecipeIngredientUnit) }}</label>
|
||||
<label for="input-ingredient-unit">{{ context.tr.t(Sentence::RecipeIngredientUnit) }}</label>
|
||||
<input class="input-ingredient-unit" type="text">
|
||||
|
||||
<label for="input-ingredient-comment">{{ tr.t(Sentence::RecipeIngredientComment) }}</label>
|
||||
<label for="input-ingredient-comment">{{ context.tr.t(Sentence::RecipeIngredientComment) }}</label>
|
||||
<input class="input-ingredient-comment" type="text">
|
||||
|
||||
<input class="input-ingredient-delete" type="button" value="{{ tr.t(Sentence::RecipeRemoveIngredient) }}">
|
||||
<input class="input-ingredient-delete" type="button" value="{{ context.tr.t(Sentence::RecipeRemoveIngredient) }}">
|
||||
</div>
|
||||
|
||||
<div class="dropzone"></div>
|
||||
|
||||
<span class="recipe-delete-confirmation">{{ tr.t(Sentence::RecipeDeleteConfirmation) }}</span>
|
||||
<span class="recipe-group-delete-confirmation">{{ tr.t(Sentence::RecipeGroupDeleteConfirmation) }}</span>
|
||||
<span class="recipe-step-delete-confirmation">{{ tr.t(Sentence::RecipeStepDeleteConfirmation) }}</span>
|
||||
<span class="recipe-ingredient-delete-confirmation">{{ tr.t(Sentence::RecipeIngredientDeleteConfirmation) }}</span>
|
||||
<span class="recipe-delete-confirmation">{{ context.tr.t(Sentence::RecipeDeleteConfirmation) }}</span>
|
||||
<span class="recipe-group-delete-confirmation">{{ context.tr.t(Sentence::RecipeGroupDeleteConfirmation) }}</span>
|
||||
<span class="recipe-step-delete-confirmation">{{ context.tr.t(Sentence::RecipeStepDeleteConfirmation) }}</span>
|
||||
<span class="recipe-ingredient-delete-confirmation">{{ context.tr.t(Sentence::RecipeIngredientDeleteConfirmation) }}</span>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -5,13 +5,13 @@
|
|||
<div class="content" id="recipe-view">
|
||||
<h2 class="recipe-title" >{{ recipe.title }}</h2>
|
||||
|
||||
{% if let Some(user) = user %}
|
||||
{% if let Some(user) = context.user %}
|
||||
{% if crate::data::model::can_user_edit_recipe(user, recipe) %}
|
||||
<a class="edit-recipe" href="/{{ tr.current_lang_code() }}/recipe/edit/{{ recipe.id }}" >Edit</a>
|
||||
<a class="edit-recipe" href="/{{ context.tr.current_lang_code() }}/recipe/edit/{{ recipe.id }}" >Edit</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<span class="add-to-planner">{{ tr.t(Sentence::CalendarAddToPlanner) }}</span>
|
||||
<span class="add-to-planner">{{ context.tr.t(Sentence::CalendarAddToPlanner) }}</span>
|
||||
|
||||
<div class="tags">
|
||||
{% for tag in recipe.tags %}
|
||||
|
|
@ -23,9 +23,9 @@
|
|||
{% when Some(servings) %}
|
||||
<span class="servings">
|
||||
{% if *servings == 1 %}
|
||||
{{ tr.t(Sentence::RecipeOneServing) }}
|
||||
{{ context.tr.t(Sentence::RecipeOneServing) }}
|
||||
{% else %}
|
||||
{{ tr.tp(Sentence::RecipeSomeServings, [Box::new(**servings)]) }}
|
||||
{{ context.tr.tp(Sentence::RecipeSomeServings, [Box::new(**servings)]) }}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% else %}
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
{% match recipe.estimated_time %}
|
||||
{% when Some(time) %}
|
||||
{{ time ~}} {{~ tr.t(Sentence::RecipeEstimatedTimeMinAbbreviation) }}
|
||||
{{ time ~}} {{~ context.tr.t(Sentence::RecipeEstimatedTimeMinAbbreviation) }}
|
||||
{% else %}
|
||||
{% endmatch %}
|
||||
|
||||
|
|
@ -41,11 +41,11 @@
|
|||
{% match recipe.difficulty %}
|
||||
{% when common::ron_api::Difficulty::Unknown %}
|
||||
{% when common::ron_api::Difficulty::Easy %}
|
||||
{{ tr.t(Sentence::RecipeDifficultyEasy) }}
|
||||
{{ context.tr.t(Sentence::RecipeDifficultyEasy) }}
|
||||
{% when common::ron_api::Difficulty::Medium %}
|
||||
{{ tr.t(Sentence::RecipeDifficultyMedium) }}
|
||||
{{ context.tr.t(Sentence::RecipeDifficultyMedium) }}
|
||||
{% when common::ron_api::Difficulty::Hard %}
|
||||
{{ tr.t(Sentence::RecipeDifficultyHard) }}
|
||||
{{ context.tr.t(Sentence::RecipeDifficultyHard) }}
|
||||
{% endmatch %}
|
||||
</span>
|
||||
|
||||
|
|
@ -86,13 +86,13 @@
|
|||
<div class="date-and-servings" >
|
||||
{% include "calendar.html" %}
|
||||
|
||||
<label for="input-servings">{{ tr.t(Sentence::RecipeServings) }}</label>
|
||||
<label for="input-servings">{{ context.tr.t(Sentence::RecipeServings) }}</label>
|
||||
<input
|
||||
id="input-servings"
|
||||
type="number"
|
||||
step="1" min="1" max="100"
|
||||
value="
|
||||
{% if let Some(user) = user %}
|
||||
{% if let Some(user) = context.user %}
|
||||
{{ user.default_servings }}
|
||||
{% else %}
|
||||
4
|
||||
|
|
@ -106,13 +106,13 @@
|
|||
checked
|
||||
>
|
||||
<label for="input-add-ingredients-to-shopping-list">
|
||||
{{ tr.t(Sentence::CalendarAddIngredientsToShoppingList) }}
|
||||
{{ context.tr.t(Sentence::CalendarAddIngredientsToShoppingList) }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<span class="calendar-add-to-planner-success">{{ tr.t(Sentence::CalendarAddToPlannerSuccess) }}</span>
|
||||
<span class="calendar-add-to-planner-already-exists">{{ tr.t(Sentence::CalendarAddToPlannerAlreadyExists) }}</span>
|
||||
<span class="calendar-date-format">{{ tr.t(Sentence::CalendarDateFormat) }}</span>
|
||||
<span class="calendar-add-to-planner-success">{{ context.tr.t(Sentence::CalendarAddToPlannerSuccess) }}</span>
|
||||
<span class="calendar-add-to-planner-already-exists">{{ context.tr.t(Sentence::CalendarAddToPlannerAlreadyExists) }}</span>
|
||||
<span class="calendar-date-format">{{ context.tr.t(Sentence::CalendarDateFormat) }}</span>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
{% macro recipe_item(id, title, is_current) %}
|
||||
<li>
|
||||
<a href="/{{ tr.current_lang_code() }}/recipe/view/{{ id }}" class="recipe-item
|
||||
<a href="/{{ context.tr.current_lang_code() }}/recipe/view/{{ id }}" class="recipe-item
|
||||
{%~ if is_current %}
|
||||
current
|
||||
{% endif %}" id="recipe-{{ id }}"
|
||||
>
|
||||
{% if title == "" %}
|
||||
{{ tr.t(Sentence::UntitledRecipe) }}
|
||||
{{ context.tr.t(Sentence::UntitledRecipe) }}
|
||||
{% else %}
|
||||
{{ title }}
|
||||
{% endif %}
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
<div id="recipes-list">
|
||||
{% if !recipes.unpublished.is_empty() %}
|
||||
{{ tr.t(Sentence::UnpublishedRecipes) }}
|
||||
{{ context.tr.t(Sentence::UnpublishedRecipes) }}
|
||||
{% endif %}
|
||||
|
||||
<nav class="recipes-list-unpublished">
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
|
||||
<div class="content" id="reset-password">
|
||||
<form action="/reset_password" method="post">
|
||||
<label for="password_field_1">Choose a new password (minimum 8 characters)</label>
|
||||
<label for="password_field_1">{{ context.tr.tp(Sentence::AskResetChooseNewPassword, [Box::new(common::consts::MIN_PASSWORD_SIZE)]) }}</label>
|
||||
<input id="password_field_1" type="password" name="password_1">
|
||||
|
||||
<label for="password_field_1">Re-enter password</label>
|
||||
<label for="password_field_1">{{ context.tr.t(Sentence::ReEnterPassword) }}</label>
|
||||
<input id="password_field_2" type="password" name="password_2">
|
||||
|
||||
{{ message_password }}
|
||||
|
|
|
|||
|
|
@ -4,17 +4,17 @@
|
|||
|
||||
<div class="content" id="sign-in">
|
||||
|
||||
<h1>{{ tr.t(Sentence::SignInTitle) }}</h1>
|
||||
<h1>{{ context.tr.t(Sentence::SignInTitle) }}</h1>
|
||||
|
||||
<form action="/signin" method="post">
|
||||
<label for="input-email">{{ tr.t(Sentence::EmailAddress) }}</label>
|
||||
<label for="input-email">{{ context.tr.t(Sentence::EmailAddress) }}</label>
|
||||
<input id="input-email" type="email" name="email" value="{{ email }}"
|
||||
autocapitalize="none" autocomplete="email" autofocus="autofocus">
|
||||
|
||||
<label for="input-password">{{ tr.t(Sentence::Password) }}</label>
|
||||
<label for="input-password">{{ context.tr.t(Sentence::Password) }}</label>
|
||||
<input id="input-password" type="password" name="password" autocomplete="current-password">
|
||||
|
||||
<input type="submit" value="{{ tr.t(Sentence::SignInMenu) }}">
|
||||
<input type="submit" value="{{ context.tr.t(Sentence::SignInMenu) }}">
|
||||
</form>
|
||||
|
||||
<span class="user-message">{{ message }}</span>
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
|
||||
<div class="content" id="sign-up">
|
||||
|
||||
<h1>{{ tr.t(Sentence::SignUpTitle) }}</h1>
|
||||
<h1>{{ context.tr.t(Sentence::SignUpTitle) }}</h1>
|
||||
|
||||
<form action="/signup" method="post">
|
||||
<label for="input-email">{{ tr.t(Sentence::EmailAddress) }}</label>
|
||||
<label for="input-email">{{ context.tr.t(Sentence::EmailAddress) }}</label>
|
||||
<input id="input-email" type="email"
|
||||
name="email" value="{{ email }}"
|
||||
autocapitalize="none" autocomplete="email" autofocus="autofocus"
|
||||
|
|
@ -15,16 +15,16 @@
|
|||
<span class="user-message">{{ message_email }}</span>
|
||||
|
||||
<label for="input-password-1">
|
||||
{{ tr.tp(Sentence::ChooseAPassword, [Box::new(common::consts::MIN_PASSWORD_SIZE)]) }}
|
||||
{{ context.tr.tp(Sentence::ChooseAPassword, [Box::new(common::consts::MIN_PASSWORD_SIZE)]) }}
|
||||
</label>
|
||||
<input id="input-password-1" type="password" name="password_1" autocomplete="new-password">
|
||||
<span></span>
|
||||
|
||||
<label for="input-password-2">{{ tr.t(Sentence::ReEnterPassword) }}</label>
|
||||
<label for="input-password-2">{{ context.tr.t(Sentence::ReEnterPassword) }}</label>
|
||||
<input id="input-password-2" type="password" name="password_2" autocomplete="new-password">
|
||||
<span class="user-message">{{ message_password }}</span>
|
||||
|
||||
<input type="submit" name="commit" value="{{ tr.t(Sentence::SignUpButton) }}">
|
||||
<input type="submit" name="commit" value="{{ context.tr.t(Sentence::SignUpButton) }}">
|
||||
</form>
|
||||
|
||||
<span class="user-message">{{ message }}</span>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
<a class="title" href="/{{ tr.current_lang_code() }}/"><img class="logo" src="/static/logo.svg" alt="logo">{{ tr.t(Sentence::MainTitle) }}</a>
|
||||
<a class="title" href="/{{ context.tr.current_lang_code() }}/"><img class="logo" src="/static/logo.svg" alt="logo">{{ context.tr.t(Sentence::MainTitle) }}</a>
|
||||
|
|
@ -50,6 +50,7 @@
|
|||
(ReEnterPassword, "Re-enter password"),
|
||||
|
||||
(LostPassword, "Lost password"),
|
||||
(AskResetChooseNewPassword, "Choose a new password (minimum {} characters)"),
|
||||
(AskResetButton, "Ask reset"),
|
||||
(AskResetAlreadyLoggedInError, "Can't ask to reset password when already logged in"),
|
||||
(AskResetEmailAlreadyResetError, "The password has already been reset for this email"),
|
||||
|
|
@ -185,6 +186,7 @@
|
|||
(ReEnterPassword, "Entrez à nouveau le mot de passe"),
|
||||
|
||||
(LostPassword, "Mot de passe perdu"),
|
||||
(AskResetChooseNewPassword, "Choisir un nouveau mot de passe (minimum {} caractères)"),
|
||||
(AskResetButton, "Demander la réinitialisation"),
|
||||
(AskResetAlreadyLoggedInError, "Impossible de demander une réinitialisation du mot de passe lorsque déjà connecté"),
|
||||
(AskResetEmailAlreadyResetError, "Le mot de passe a déjà été réinitialisé pour cette adresse email"),
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
pub const MIN_PASSWORD_SIZE: usize = 8;
|
||||
pub const COOKIE_DARK_THEME: &str = "dark_theme";
|
||||
|
|
|
|||
2
frontend/.cargo/config.toml
Normal file
2
frontend/.cargo/config.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
|
|
@ -3,7 +3,7 @@ use gloo::{console::log, events::EventListener, utils::window};
|
|||
use utils::by_id;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use web_sys::{HtmlElement, HtmlSelectElement};
|
||||
use web_sys::{HtmlElement, HtmlInputElement, HtmlSelectElement};
|
||||
|
||||
use crate::utils::selector;
|
||||
|
||||
|
|
@ -58,6 +58,7 @@ pub fn main() -> Result<(), JsValue> {
|
|||
_ => log!("Path unknown: ", location),
|
||||
}
|
||||
|
||||
// Language handling.
|
||||
let select_language: HtmlSelectElement = by_id("select-website-language");
|
||||
EventListener::new(&select_language.clone(), "input", move |_event| {
|
||||
let lang = select_language.value();
|
||||
|
|
@ -79,5 +80,23 @@ pub fn main() -> Result<(), JsValue> {
|
|||
})
|
||||
.forget();
|
||||
|
||||
// Dark/light theme handling.
|
||||
let toggle_theme: HtmlInputElement = selector("#toggle-theme input");
|
||||
EventListener::new(&toggle_theme.clone(), "change", move |_event| {
|
||||
wasm_cookies::set(
|
||||
common::consts::COOKIE_DARK_THEME,
|
||||
&(!toggle_theme.checked()).to_string(),
|
||||
&wasm_cookies::CookieOptions {
|
||||
path: Some("/"),
|
||||
domain: None,
|
||||
expires: None,
|
||||
secure: false,
|
||||
same_site: wasm_cookies::SameSite::Strict,
|
||||
},
|
||||
);
|
||||
window().location().reload().unwrap();
|
||||
})
|
||||
.forget();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue