/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/
\r
axum = { version = "0.8", features = ["macros"] }\r
axum-extra = { version = "0.10", features = ["cookie", "query"] }\r
-tokio = { version = "1", features = ["full"] }\r
+tokio = { version = "1", features = ["signal", "rt-multi-thread"] }\r
tower = { version = "0.5", features = ["util", "limit", "buffer"] }\r
tower-http = { version = "0.6", features = ["fs", "trace"] }\r
\r
"tokio1-rustls-tls",\r
] }\r
\r
-derive_more = { version = "2", features = ["full"] }\r
thiserror = "2"\r
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)
+ };
- 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);
+ 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);
+ }
}
+
+ run_sass("style_light");
+ run_sass("style_dark");
}
--- /dev/null
+@use 'sass:color';
+
+@use 'toast.scss';
+@use 'modal-dialog.scss';
+@use 'calendar.scss';
+
+$dark-theme: false !default;
+
+$color-1: #B29B89;
+$color-2: #89B29B;
+$color-3: #9B89B2;
+
+$text-color: color.adjust($color-1, $lightness: -30%);
+$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;
+}
+
+a {
+ color: $link-color;
+ text-decoration: none;
+
+ &:hover {
+ color: $link-hover-color;
+ }
+}
+
+body {
+ display: flex;
+ flex-direction: column;
+
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ color: $text-color;
+ background-color: $color-1;
+ margin: 0px;
+
+ .user-message {
+ font-weight: bold;
+ }
+
+ .footer-container {
+ align-self: center;
+ font-size: 0.7em;
+ }
+
+ .drag-handle {
+ width: 20px;
+ height: 20px;
+ display: inline-block;
+ vertical-align: bottom;
+ background-color: blue;
+ }
+
+ img {
+ border: 0px;
+ }
+
+ .header-container {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+
+ .title {
+ font-size: 180%;
+ font-style: italic;
+
+ .logo {
+ width: 50px;
+ height: 50px;
+ vertical-align: bottom;
+ margin: 0px 10px 0px 0px;
+ }
+ }
+
+ .header-menu {
+ align-self: flex-end;
+
+ .create-recipe {
+ border: 0.1em solid $color-3;
+ padding: 2px 8px 2px 8px;
+ border-radius: 0.5em;
+ }
+ }
+
+ #select-website-language {
+ padding: 5px;
+ }
+ }
+
+ .main-container {
+ display: flex;
+ flex-direction: row;
+
+ #recipes-list {
+ hr {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ height: 1px;
+ color: $color-3;
+ background: $color-3;
+ font-size: 0;
+ border: 0;
+ }
+
+ .recipe-item {
+ white-space: preserve nowrap;
+ padding: 4px;
+ // Transparent border: to keep same size than '.recipe-item-current'.
+ border: 0.1em solid rgba(0, 0, 0, 0);
+
+ &.current {
+ white-space: preserve nowrap;
+ padding: 4px;
+ border: 0.1em solid $color-3;
+
+ border-radius: 0.5em;
+ color: $text-highlight;
+ background-color: $color-2;
+ }
+ }
+ }
+
+ .content {
+ flex-grow: 1;
+
+ margin-left: 0px;
+
+ background-color: $color-2;
+ border: 0.1em solid $color-3;
+ border-radius: 1em;
+ padding: 0.8em;
+
+ h1 {
+ text-align: center;
+ }
+ }
+
+ #hidden-templates {
+ display: none;
+ }
+
+ #recipe-edit {
+ display: grid;
+
+ .drag-handle {
+ cursor: move;
+ }
+
+ .group {
+ border: 0.1em solid color.adjust($color-3, $lightness: +30%);
+ margin-top: 0px;
+ margin-bottom: 0px;
+ }
+
+ .step {
+ border: 0.1em solid color.adjust($color-3, $lightness: +30%);
+ margin-top: 0px;
+ margin-bottom: 0px;
+ }
+
+ .ingredient {
+ border: 0.1em solid color.adjust($color-3, $lightness: +30%);
+ margin-top: 0px;
+ margin-bottom: 0px;
+ }
+
+ .dropzone {
+ height: 10px;
+ margin-top: 0px;
+ margin-bottom: 0px;
+
+ background-color: white;
+
+ &.active {
+ background-color: blue;
+ }
+
+ &.hover {
+ background-color: red;
+ }
+ }
+ }
+
+ form {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 3px;
+
+ input,
+ button {
+ // background-color: rgb(52, 40, 85);
+ border-width: 1px;
+ // border-color: white;
+ // color: white;
+ }
+ }
+
+ #sign-up form {
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+
+ input[type="submit"] {
+ grid-column: 2
+ }
+ }
+
+ #sign-in form {
+ input[type="submit"] {
+ grid-column: 2
+ }
+ }
+
+ #ask-reset-password form {
+ grid-template-columns: auto 1fr auto;
+
+ input[type="submit"] {
+ grid-column: 2
+ }
+ }
+
+ #user-edit form {
+ grid-template-columns: auto 1fr auto;
+
+ input[type="submit"] {
+ grid-column: 2
+ }
+ }
+
+ // #sign-in {
+
+ // }
+
+ // #user-edit {
+ // .label-name {
+ // grid-column: 1;
+ // grid-row: 1;
+ // }
+
+ // .input-name {
+ // grid-column: 2;
+ // grid-row: 1;
+ // }
+
+ // .label-password-1 {
+ // grid-column: 1;
+ // grid-row: 2;
+ // }
+
+ // .input-password-1 {
+ // grid-column: 2;
+ // grid-row: 2;
+ // }
+
+ // .label-password-2 {
+ // grid-column: 1;
+ // grid-row: 3;
+ // }
+
+ // .input-password-2 {
+ // grid-column: 2;
+ // grid-row: 3;
+ // }
+
+ // .button-save {
+ // grid-column: 2;
+ // grid-row: 4;
+ // width: fit-content;
+ // justify-self: flex-end;
+ // }
+ // }
+ }
+}
+
+// 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)
+ }
+ }
+}
\ No newline at end of file
+++ /dev/null
-@use 'sass:color';
-
-@use 'toast.scss';
-@use 'modal-dialog.scss';
-@use 'calendar.scss';
-
-$color-1: #B29B89;
-$color-2: #89B29B;
-$color-3: #9B89B2;
-
-$text-color: color.adjust($color-1, $lightness: -30%);
-$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%);
-
-* {
- margin: 5px;
- padding: 0px;
-}
-
-a {
- color: $link-color;
- text-decoration: none;
-
- &:hover {
- color: $link-hover-color;
- }
-}
-
-body {
- display: flex;
- flex-direction: column;
-
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- color: $text-color;
- background-color: $color-1;
- margin: 0px;
-
- .user-message {
- font-weight: bold;
- }
-
- .footer-container {
- align-self: center;
- font-size: 0.7em;
- }
-
- .drag-handle {
- width: 20px;
- height: 20px;
- display: inline-block;
- vertical-align: bottom;
- background-color: blue;
- }
-
- img {
- border: 0px;
- }
-
- .header-container {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
-
- .title {
- font-size: 180%;
- font-style: italic;
-
- .logo {
- width: 50px;
- height: 50px;
- vertical-align: bottom;
- margin: 0px 10px 0px 0px;
- }
- }
-
- .header-menu {
- align-self: flex-end;
-
- .create-recipe {
- border: 0.1em solid $color-3;
- padding: 2px 8px 2px 8px;
- border-radius: 0.5em;
- }
- }
-
- #select-website-language {
- padding: 5px;
- }
- }
-
- .main-container {
- display: flex;
- flex-direction: row;
-
- #recipes-list {
- hr {
- margin-top: 10px;
- margin-bottom: 10px;
- height: 1px;
- color: $color-3;
- background: $color-3;
- font-size: 0;
- border: 0;
- }
-
- .recipe-item {
- white-space: preserve nowrap;
- padding: 4px;
- // Transparent border: to keep same size than '.recipe-item-current'.
- border: 0.1em solid rgba(0, 0, 0, 0);
-
- &.current {
- white-space: preserve nowrap;
- padding: 4px;
- border: 0.1em solid $color-3;
-
- border-radius: 0.5em;
- color: $text-highlight;
- background-color: $color-2;
- }
- }
- }
-
- .content {
- flex-grow: 1;
-
- margin-left: 0px;
-
- background-color: $color-2;
- border: 0.1em solid $color-3;
- border-radius: 1em;
- padding: 0.8em;
-
- h1 {
- text-align: center;
- }
- }
-
- #hidden-templates {
- display: none;
- }
-
- #recipe-edit {
- display: grid;
-
- .drag-handle {
- cursor: move;
- }
-
- .group {
- border: 0.1em solid color.adjust($color-3, $lightness: +30%);
- margin-top: 0px;
- margin-bottom: 0px;
- }
-
- .step {
- border: 0.1em solid color.adjust($color-3, $lightness: +30%);
- margin-top: 0px;
- margin-bottom: 0px;
- }
-
- .ingredient {
- border: 0.1em solid color.adjust($color-3, $lightness: +30%);
- margin-top: 0px;
- margin-bottom: 0px;
- }
-
- .dropzone {
- height: 10px;
- margin-top: 0px;
- margin-bottom: 0px;
-
- background-color: white;
-
- &.active {
- background-color: blue;
- }
-
- &.hover {
- background-color: red;
- }
- }
- }
-
- form {
- display: grid;
- grid-template-columns: auto 1fr;
- gap: 3px;
-
- input,
- button {
- // background-color: rgb(52, 40, 85);
- border-width: 1px;
- // border-color: white;
- // color: white;
- }
- }
-
- #sign-up form {
- display: grid;
- grid-template-columns: auto 1fr auto;
-
- input[type="submit"] {
- grid-column: 2
- }
- }
-
- #sign-in form {
- input[type="submit"] {
- grid-column: 2
- }
- }
-
- #ask-reset-password form {
- grid-template-columns: auto 1fr auto;
-
- input[type="submit"] {
- grid-column: 2
- }
- }
-
- #user-edit form {
- grid-template-columns: auto 1fr auto;
-
- input[type="submit"] {
- grid-column: 2
- }
- }
-
- // #sign-in {
-
- // }
-
- // #user-edit {
- // .label-name {
- // grid-column: 1;
- // grid-row: 1;
- // }
-
- // .input-name {
- // grid-column: 2;
- // grid-row: 1;
- // }
-
- // .label-password-1 {
- // grid-column: 1;
- // grid-row: 2;
- // }
-
- // .input-password-1 {
- // grid-column: 2;
- // grid-row: 2;
- // }
-
- // .label-password-2 {
- // grid-column: 1;
- // grid-row: 3;
- // }
-
- // .input-password-2 {
- // grid-column: 2;
- // grid-row: 3;
- // }
-
- // .button-save {
- // grid-column: 2;
- // grid-row: 4;
- // width: fit-content;
- // justify-self: flex-end;
- // }
- // }
- }
-}
\ No newline at end of file
--- /dev/null
+@use 'main.scss' with ($dark-theme: true);
\ No newline at end of file
--- /dev/null
+@use 'main.scss' with ($dark-theme: false);
\ No newline at end of file
use askama::Template;
use crate::{
+ Context,
data::model,
translation::{self, Sentence, Tr},
};
#[derive(Template)]
#[template(path = "home.html")]
pub struct HomeTemplate {
- pub user: Option<model::User>,
- pub tr: Tr,
+ pub context: Context,
pub recipes: Recipes,
}
#[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.
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,
}
user: Option<model::User>,
) -> MessageTemplate<'a> {
MessageTemplate {
- user,
- tr,
+ context: Context {
+ user,
+ tr,
+ dark_theme: false,
+ },
message,
as_code: false,
}
#[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,
#[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,
#[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,
#[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,
#[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,
#[derive(Template)]
#[template(path = "recipe_view.html")]
pub struct RecipeViewTemplate {
- pub user: Option<model::User>,
- pub tr: Tr,
+ pub context: Context,
pub recipes: Recipes,
#[derive(Template)]
#[template(path = "recipe_edit.html")]
pub struct RecipeEditTemplate {
- pub user: Option<model::User>,
- pub tr: Tr,
+ pub context: Context,
pub recipes: Recipes,
#[derive(Template)]
#[template(path = "recipes_list_fragment.html")]
pub struct RecipesListFragmentTemplate {
- pub tr: Tr,
+ pub context: Context,
pub recipes: Recipes,
}
#[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() {
.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"))
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>);
/// - 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());
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)
}
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 {
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?
},
current_id: current_recipe.current_recipe_id,
};
- Ok(Html(RecipesListFragmentTemplate { tr, recipes }.render()?))
+ Ok(Html(
+ RecipesListFragmentTemplate { context, recipes }.render()?,
+ ))
}
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;
// 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> {
};
return Ok(Html(
MessageTemplate {
- user: None,
+ context,
message: &message,
as_code: true,
- tr,
}
.render()?,
)
#[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?
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()?),
))
}
// 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)
Ok(Html(
RecipeEditTemplate {
- user: Some(user),
- tr,
+ context,
recipes,
recipe,
}
)
.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()?,
)
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?
Ok(Html(
RecipeViewTemplate {
- user,
- tr,
+ context,
recipes,
recipe,
}
.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()),
}
// use tracing::{event, Level};
use crate::{
+ Context,
data::{self, db},
- model,
ron_extractor::ExtractRon,
ron_utils::{ron_error, ron_response_ok},
};
#[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)
#[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,
#[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,
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;
#[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?;
}
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::*;
#[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?;
#[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?;
#[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?;
#[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?;
#[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,
#[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)
}
#[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?;
#[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()
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?;
#[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?;
#[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)
}
#[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 }))
#[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)
}
#[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)
}
#[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?;
#[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)
}
#[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 }))
#[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)
}
#[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)
}
#[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)
}
#[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 }))
}
#[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)
}
#[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?;
#[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?;
#[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?;
#[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?;
#[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)
}
use common::ron_api;
use crate::{
+ Context,
data::db,
model,
ron_extractor::ExtractRon,
#[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)
#[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?,
))
};
use axum_extra::extract::{
Host, Query,
- cookie::{Cookie, CookieJar},
+ cookie::{self, Cookie, CookieJar},
};
use chrono::Duration;
use lettre::Address;
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 ///
#[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: "",
)
.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())
}
}
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()?,
)
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
.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={}",
.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)
}
}
}
#[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()?,
),
.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()?,
jar,
Html(
MessageTemplate::new_with_user(
- tr.t(Sentence::SignUpValidationExpired),
- tr,
- user,
+ context.tr.t(Sentence::SignUpValidationExpired),
+ context.tr,
+ context.user,
)
.render()?,
),
jar,
Html(
MessageTemplate::new_with_user(
- tr.t(Sentence::SignUpValidationErrorTryAgain),
- tr,
- user,
+ context.tr.t(Sentence::SignUpValidationErrorTryAgain),
+ context.tr,
+ context.user,
)
.render()?,
),
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()?,
),
)),
}
/// 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: "",
}
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)> {
jar,
Html(
SignInFormTemplate {
- user,
email: &form_data.email,
- message: tr.t(Sentence::AccountMustBeValidatedFirst),
- tr,
+ message: context.tr.t(Sentence::AccountMustBeValidatedFirst),
+ context,
}
.render()?,
)
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(),
))
}
}
#[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());
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: "",
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()?,
)
return error_response(
AskResetPasswordError::InvalidEmail,
&form_data.email,
- user,
- tr,
+ context,
);
}
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={}",
.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(_) => {
error_response(
AskResetPasswordError::UnableSendEmail,
&form_data.email,
- user,
- tr,
+ context,
)
}
}
error_response(
AskResetPasswordError::DatabaseError,
&form_data.email,
- user,
- tr,
+ context,
)
}
}
#[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") {
{
Ok(Html(
ResetPasswordTemplate {
- user,
- tr,
+ context,
reset_token,
message: "",
message_password: "",
.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())
}
#[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()?,
)
}
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
.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,
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()
})
}
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()?,
)
}
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 {
.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={}",
.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);
}
}
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()?,
),
.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()?,
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()?,
),
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()?,
),
)),
}
// Reset password page.
LostPassword,
+ AskResetChooseNewPassword,
AskResetButton,
AskResetAlreadyLoggedInError,
AskResetEmailAlreadyResetError,
pub const DEFAULT_LANGUAGE_CODE: &str = "en";
pub const PLACEHOLDER_SUBSTITUTE: &str = "{}";
-#[derive(Clone)]
+#[derive(Debug, Clone)]
pub struct Tr {
lang: &'static Language,
}
{% 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>
<!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>
{% 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>
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>
Sentence::CalendarSaturdayAbbreviation,
Sentence::CalendarSundayAbbreviation,
] %}
- <li class="weekday">{{ tr.t(*day) }}</li>
+ <li class="weekday">{{ context.tr.t(*day) }}</li>
{% endfor %}
</ul>
<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>
\ No newline at end of file
<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 %}
\ No newline at end of file
{% 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"
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"
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>
{% 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"
{{ 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"
{{ 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"
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 }}"
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 %}
\ No newline at end of file
<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 %}
{% 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 %}
{% match recipe.estimated_time %}
{% when Some(time) %}
- {{ time ~}} {{~ tr.t(Sentence::RecipeEstimatedTimeMinAbbreviation) }}
+ {{ time ~}} {{~ context.tr.t(Sentence::RecipeEstimatedTimeMinAbbreviation) }}
{% else %}
{% endmatch %}
{% 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>
<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
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 %}
\ No newline at end of file
{% 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 %}
<div id="recipes-list">
{% if !recipes.unpublished.is_empty() %}
- {{ tr.t(Sentence::UnpublishedRecipes) }}
+ {{ context.tr.t(Sentence::UnpublishedRecipes) }}
{% endif %}
<nav class="recipes-list-unpublished">
<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 }}
<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>
<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"
<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>
-<a class="title" href="/{{ tr.current_lang_code() }}/"><img class="logo" src="/static/logo.svg" alt="logo">{{ tr.t(Sentence::MainTitle) }}</a>
\ No newline at end of file
+<a class="title" href="/{{ context.tr.current_lang_code() }}/"><img class="logo" src="/static/logo.svg" alt="logo">{{ context.tr.t(Sentence::MainTitle) }}</a>
\ No newline at end of file
(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"),
(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"),
pub const MIN_PASSWORD_SIZE: usize = 8;
+pub const COOKIE_DARK_THEME: &str = "dark_theme";
--- /dev/null
+[build]
+target = "wasm32-unknown-unknown"
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;
_ => 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();
})
.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(())
}