Progress on rework
This commit is contained in:
parent
e14c1f0918
commit
4d4fab60cb
4 changed files with 321 additions and 9 deletions
3
public/regalade.css
Normal file
3
public/regalade.css
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.inline {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use axum::{
|
use axum::{
|
||||||
async_trait,
|
async_trait,
|
||||||
extract::{FromRef, FromRequestParts, State},
|
extract::{FromRef, FromRequestParts, Path, State},
|
||||||
http::request::Parts,
|
http::request::Parts,
|
||||||
response::Redirect,
|
response::Redirect,
|
||||||
Form,
|
Form,
|
||||||
};
|
};
|
||||||
use maud::{html, Markup};
|
use maud::{html, Markup};
|
||||||
use sea_orm::{prelude::*, ActiveValue};
|
use sea_orm::{prelude::*, ActiveValue, DatabaseTransaction, TransactionTrait};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tower_sessions::Session;
|
use tower_sessions::Session;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
@ -132,14 +132,28 @@ pub(super) async fn household_selection(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub(super) struct CreateHousehold {
|
pub(super) struct HouseholdName {
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) async fn rename(
|
||||||
|
household: CurrentHousehold,
|
||||||
|
state: State<AppState>,
|
||||||
|
Form(form): Form<HouseholdName>,
|
||||||
|
) -> Result<Redirect, RouteError> {
|
||||||
|
let mut household: household::ActiveModel = household.0.into();
|
||||||
|
|
||||||
|
household.name = ActiveValue::Set(form.name);
|
||||||
|
|
||||||
|
household.update(&state.db).await?;
|
||||||
|
|
||||||
|
Ok(Redirect::to("/"))
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) async fn create_household(
|
pub(super) async fn create_household(
|
||||||
user: AuthenticatedUser,
|
user: AuthenticatedUser,
|
||||||
state: State<AppState>,
|
state: State<AppState>,
|
||||||
Form(form): Form<CreateHousehold>,
|
Form(form): Form<HouseholdName>,
|
||||||
) -> Result<Redirect, RouteError> {
|
) -> Result<Redirect, RouteError> {
|
||||||
let household = household::ActiveModel {
|
let household = household::ActiveModel {
|
||||||
name: ActiveValue::Set(form.name),
|
name: ActiveValue::Set(form.name),
|
||||||
|
|
@ -157,3 +171,73 @@ pub(super) async fn create_household(
|
||||||
|
|
||||||
Ok(Redirect::to("/household/select"))
|
Ok(Redirect::to("/household/select"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) async fn select_household(
|
||||||
|
user: AuthenticatedUser,
|
||||||
|
state: State<AppState>,
|
||||||
|
session: Session,
|
||||||
|
Path(household): Path<Uuid>,
|
||||||
|
) -> Result<Redirect, RouteError> {
|
||||||
|
let hs_is_authorized = user
|
||||||
|
.model
|
||||||
|
.find_related(Household)
|
||||||
|
.filter(household::Column::Id.eq(household))
|
||||||
|
.count(&state.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if hs_is_authorized == 0 {
|
||||||
|
return Err(RouteError::RessourceNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.insert("household", household).await?;
|
||||||
|
|
||||||
|
Ok(Redirect::to("/"))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_household(
|
||||||
|
household: household::Model,
|
||||||
|
txn: &DatabaseTransaction,
|
||||||
|
) -> Result<(), RouteError> {
|
||||||
|
for ingredient in household.find_related(Ingredient).all(txn).await? {
|
||||||
|
ingredient.delete(txn).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
household.delete(txn).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn leave(
|
||||||
|
household: CurrentHousehold,
|
||||||
|
user: AuthenticatedUser,
|
||||||
|
session: Session,
|
||||||
|
state: State<AppState>,
|
||||||
|
) -> Result<Redirect, RouteError> {
|
||||||
|
state
|
||||||
|
.db
|
||||||
|
.transaction(|txn| {
|
||||||
|
Box::pin(async move {
|
||||||
|
HouseholdMembers::delete_by_id((household.0.id, user.model.id))
|
||||||
|
.exec(txn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let Some(household) = Household::find_by_id(household.0.id)
|
||||||
|
.one(txn)
|
||||||
|
.await? else {
|
||||||
|
return Err(RouteError::InvalidRequest("No such household".into()));
|
||||||
|
};
|
||||||
|
|
||||||
|
let member_count = household.find_related(User).count(txn).await?;
|
||||||
|
if member_count == 0 {
|
||||||
|
delete_household(household, txn).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
session.remove::<Uuid>("household").await?;
|
||||||
|
|
||||||
|
Ok(Redirect::to("/household/select"))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,10 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use crate::entity::{prelude::*, user};
|
use crate::entity::{prelude::*, user};
|
||||||
|
|
||||||
use self::household::CurrentHousehold;
|
use self::{household::CurrentHousehold, sidebar::SidebarLocation};
|
||||||
|
|
||||||
mod household;
|
mod household;
|
||||||
|
mod sidebar;
|
||||||
|
|
||||||
type AppState = Arc<crate::AppState>;
|
type AppState = Arc<crate::AppState>;
|
||||||
|
|
||||||
|
|
@ -36,6 +37,11 @@ pub fn base_page_with_head(body: Markup, head: Option<Markup>) -> Markup {
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
|
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
|
||||||
crossorigin="anonymous";
|
crossorigin="anonymous";
|
||||||
|
link rel="stylesheet" href="/regalade.css";
|
||||||
|
link rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
||||||
|
integrity="sha384-XGjxtQfXaH2tnPFa9x+ruJTuLE3Aa6LhHSWRr1XeTyhezb4abCG4ccI5AkVDxqC+"
|
||||||
|
crossorigin="anonymous";
|
||||||
@if let Some(head) = head {
|
@if let Some(head) = head {
|
||||||
(head)
|
(head)
|
||||||
}
|
}
|
||||||
|
|
@ -117,6 +123,15 @@ impl From<DbErr> for Box<RouteError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<TransactionError<RouteError>> for RouteError {
|
||||||
|
fn from(value: TransactionError<RouteError>) -> Self {
|
||||||
|
match value {
|
||||||
|
TransactionError::Connection(e) => e.into(),
|
||||||
|
TransactionError::Transaction(e) => e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IntoResponse for RouteError {
|
impl IntoResponse for RouteError {
|
||||||
fn into_response(self) -> axum::response::Response {
|
fn into_response(self) -> axum::response::Response {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -267,10 +282,52 @@ fn not_found() -> (StatusCode, Markup) {
|
||||||
error_page(StatusCode::NOT_FOUND, "Page not found")
|
error_page(StatusCode::NOT_FOUND, "Page not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn index(_user: AuthenticatedUser, household: CurrentHousehold) -> Markup {
|
fn confirm_danger_modal(id: &str, inner: &str, action: &str, title: &str) -> Markup {
|
||||||
base_page(html! {
|
html! {
|
||||||
"Hello world in " (household.0.name) "!"
|
.modal
|
||||||
})
|
#(id)
|
||||||
|
tabindex="-1"
|
||||||
|
aria-labelledby={(id) "Label"}
|
||||||
|
aria-hidden="true" {
|
||||||
|
.modal-dialog.modal-dialog-centered {
|
||||||
|
.modal-content {
|
||||||
|
.modal-header {
|
||||||
|
h1 .modal-title."fs-5" #{(id) "Label"} { (title) }
|
||||||
|
button
|
||||||
|
.btn-close
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
aria-label="Cancel" {}
|
||||||
|
}
|
||||||
|
.modal-body {
|
||||||
|
(inner)
|
||||||
|
}
|
||||||
|
.modal-footer {
|
||||||
|
button .btn.btn-secondary data-bs-dismiss="modal" { "Cancel" }
|
||||||
|
form action=(action) method="post" .inline {
|
||||||
|
button type="submit" .btn.btn-danger { "Confirm" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn index(user: AuthenticatedUser, household: CurrentHousehold) -> Markup {
|
||||||
|
sidebar::sidebar(
|
||||||
|
SidebarLocation::Home,
|
||||||
|
&household,
|
||||||
|
&user,
|
||||||
|
html! {
|
||||||
|
"Hello world in " (household.0.name) "!"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn logout(session: Session) -> Result<Redirect, RouteError> {
|
||||||
|
session.delete().await?;
|
||||||
|
|
||||||
|
Ok(Redirect::to("/login"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(Serialize, Deserialize, Debug)]
|
// #[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|
@ -308,8 +365,12 @@ pub(crate) fn router() -> Router<AppState> {
|
||||||
router
|
router
|
||||||
.route("/", get(index))
|
.route("/", get(index))
|
||||||
.route("/login", get(oidc_login))
|
.route("/login", get(oidc_login))
|
||||||
|
.route("/logout", get(logout))
|
||||||
.route("/household/select", get(household::household_selection))
|
.route("/household/select", get(household::household_selection))
|
||||||
|
.route("/household/select/:id", get(household::select_household))
|
||||||
.route("/household/create", post(household::create_household))
|
.route("/household/create", post(household::create_household))
|
||||||
|
.route("/household/leave", post(household::leave))
|
||||||
|
.route("/household/rename", post(household::rename))
|
||||||
.route("/login/redirect/:id", get(oidc_login_finish))
|
.route("/login/redirect/:id", get(oidc_login_finish))
|
||||||
.fallback_service(ServeDir::new(public).fallback((|| async { not_found() }).into_service()))
|
.fallback_service(ServeDir::new(public).fallback((|| async { not_found() }).into_service()))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
164
src/app/sidebar.rs
Normal file
164
src/app/sidebar.rs
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
use maud::{html, Markup};
|
||||||
|
|
||||||
|
use super::{base_page, confirm_danger_modal, household::CurrentHousehold, AuthenticatedUser};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub(super) enum SidebarLocation {
|
||||||
|
Home,
|
||||||
|
RecipeList,
|
||||||
|
Ingredients,
|
||||||
|
RecipeCreator,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SidebarLocation {
|
||||||
|
fn to(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
SidebarLocation::Home => "/",
|
||||||
|
SidebarLocation::RecipeList => "/recipes",
|
||||||
|
SidebarLocation::Ingredients => "/ingredients",
|
||||||
|
SidebarLocation::RecipeCreator => "/recipes/create",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MenuEntry {
|
||||||
|
icon: &'static str,
|
||||||
|
label: &'static str,
|
||||||
|
location: SidebarLocation,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rename_hs_modal(current: &str) -> Markup {
|
||||||
|
html! {
|
||||||
|
.modal.fade
|
||||||
|
#renameHsModal
|
||||||
|
tabindex="-1"
|
||||||
|
aria-labelledby="renameHsModalLabel"
|
||||||
|
aria-hidden="true" {
|
||||||
|
.modal-dialog.modal-dialog-centered {
|
||||||
|
.modal-content {
|
||||||
|
.modal-header {
|
||||||
|
h1 .modal-title."fs-5" #renameHsModalLabel { "Rename Household" }
|
||||||
|
input
|
||||||
|
type="reset"
|
||||||
|
form="renameHsModalForm"
|
||||||
|
.btn-close
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
aria-label="Close"
|
||||||
|
value="";
|
||||||
|
}
|
||||||
|
.modal-body {
|
||||||
|
form #renameHsModalForm method="post" action="/household/rename" {
|
||||||
|
.form-floating {
|
||||||
|
input
|
||||||
|
.form-control
|
||||||
|
#renameHsName
|
||||||
|
placeholder="Household name"
|
||||||
|
name="name"
|
||||||
|
value=(current);
|
||||||
|
label for="renameHsName" { "New household name" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-footer {
|
||||||
|
input
|
||||||
|
type="reset"
|
||||||
|
form="renameHsModalForm"
|
||||||
|
.btn.btn-danger
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
value="Cancel";
|
||||||
|
input
|
||||||
|
type="submit"
|
||||||
|
form="renameHsModalForm"
|
||||||
|
.btn.btn-primary
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
value="Rename";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn sidebar(
|
||||||
|
current: SidebarLocation,
|
||||||
|
household: &CurrentHousehold,
|
||||||
|
user: &AuthenticatedUser,
|
||||||
|
inner: Markup,
|
||||||
|
) -> Markup {
|
||||||
|
let entries = &[
|
||||||
|
MenuEntry {
|
||||||
|
label: "Home",
|
||||||
|
icon: "bi-house",
|
||||||
|
location: SidebarLocation::Home,
|
||||||
|
},
|
||||||
|
MenuEntry {
|
||||||
|
label: "Recipes",
|
||||||
|
icon: "bi-book",
|
||||||
|
location: SidebarLocation::RecipeList,
|
||||||
|
},
|
||||||
|
MenuEntry {
|
||||||
|
label: "Ingredients",
|
||||||
|
icon: "bi-egg-fill",
|
||||||
|
location: SidebarLocation::Ingredients,
|
||||||
|
},
|
||||||
|
MenuEntry {
|
||||||
|
label: "New Recipe",
|
||||||
|
icon: "bi-clipboard2-plus-fill",
|
||||||
|
location: SidebarLocation::RecipeCreator,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
base_page(html! {
|
||||||
|
.container-fluid {
|
||||||
|
.row.flex-nowrap {
|
||||||
|
.col-auto."col-md-3"."col-xl-2"."px-sm-2"."px-0".bg-dark-subtle {
|
||||||
|
.d-flex.flex-column.align-items-center.align-items-sm-start."px-sm-3"."px-1"."pt-2".text-white."min-vh-100" {
|
||||||
|
ul #menu .nav.nav-pills.flex-column.mb-sm-auto."mb-0".align-items-center.align-items-sm-start."w-100" {
|
||||||
|
@for entry in entries {
|
||||||
|
li .nav-item."w-100" {
|
||||||
|
a href=(entry.location.to())
|
||||||
|
class={"nav-link text-white" (
|
||||||
|
(entry.location == current).then_some(" active").unwrap_or("")
|
||||||
|
)} {
|
||||||
|
i class={"fs-4 " (entry.icon)} {}
|
||||||
|
span ."ms-2".d-none.d-sm-inline { (entry.label) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hr ."w-100";
|
||||||
|
(confirm_danger_modal(
|
||||||
|
"leaveModal",
|
||||||
|
&format!("Are you sure you want to leave the household {}", household.0.name),
|
||||||
|
"/household/leave",
|
||||||
|
"Leave Household",
|
||||||
|
))
|
||||||
|
(rename_hs_modal(&household.0.name))
|
||||||
|
.dropdown {
|
||||||
|
a href="#" "data-bs-toggle"="dropdown" "aria-expanded"="false"
|
||||||
|
.d-flex.align-items-center.text-white.text-decoration-none.dropdown-toggle {
|
||||||
|
i ."fs-4".bi-house-door-fill {}
|
||||||
|
strong ."ms-2".d-none.d-sm-inline {
|
||||||
|
(household.0.name) " (" (user.model.name) ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ul .dropdown-menu {
|
||||||
|
li { a .dropdown-item href="/logout" {"Logout"}}
|
||||||
|
hr;
|
||||||
|
li { a .dropdown-item href="#" data-bs-toggle="modal" data-bs-target="#leaveModal" {
|
||||||
|
"Leave household"
|
||||||
|
}}
|
||||||
|
li { a .dropdown-item href="#" data-bs-toggle="modal" data-bs-target="#renameHsModal" {
|
||||||
|
"Rename household"
|
||||||
|
}}
|
||||||
|
hr;
|
||||||
|
li { a .dropdown-item href="/household/select" {"Change household"}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.col."py-3".overflow-scroll."vh-100" { (inner) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue