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::{
|
||||
async_trait,
|
||||
extract::{FromRef, FromRequestParts, State},
|
||||
extract::{FromRef, FromRequestParts, Path, State},
|
||||
http::request::Parts,
|
||||
response::Redirect,
|
||||
Form,
|
||||
};
|
||||
use maud::{html, Markup};
|
||||
use sea_orm::{prelude::*, ActiveValue};
|
||||
use sea_orm::{prelude::*, ActiveValue, DatabaseTransaction, TransactionTrait};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower_sessions::Session;
|
||||
use uuid::Uuid;
|
||||
|
|
@ -132,14 +132,28 @@ pub(super) async fn household_selection(
|
|||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub(super) struct CreateHousehold {
|
||||
pub(super) struct HouseholdName {
|
||||
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(
|
||||
user: AuthenticatedUser,
|
||||
state: State<AppState>,
|
||||
Form(form): Form<CreateHousehold>,
|
||||
Form(form): Form<HouseholdName>,
|
||||
) -> Result<Redirect, RouteError> {
|
||||
let household = household::ActiveModel {
|
||||
name: ActiveValue::Set(form.name),
|
||||
|
|
@ -157,3 +171,73 @@ pub(super) async fn create_household(
|
|||
|
||||
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 self::household::CurrentHousehold;
|
||||
use self::{household::CurrentHousehold, sidebar::SidebarLocation};
|
||||
|
||||
mod household;
|
||||
mod sidebar;
|
||||
|
||||
type AppState = Arc<crate::AppState>;
|
||||
|
||||
|
|
@ -36,6 +37,11 @@ pub fn base_page_with_head(body: Markup, head: Option<Markup>) -> Markup {
|
|||
rel="stylesheet"
|
||||
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
|
||||
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 {
|
||||
(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 {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
match self {
|
||||
|
|
@ -267,10 +282,52 @@ fn not_found() -> (StatusCode, Markup) {
|
|||
error_page(StatusCode::NOT_FOUND, "Page not found")
|
||||
}
|
||||
|
||||
async fn index(_user: AuthenticatedUser, household: CurrentHousehold) -> Markup {
|
||||
base_page(html! {
|
||||
"Hello world in " (household.0.name) "!"
|
||||
})
|
||||
fn confirm_danger_modal(id: &str, inner: &str, action: &str, title: &str) -> Markup {
|
||||
html! {
|
||||
.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)]
|
||||
|
|
@ -308,8 +365,12 @@ pub(crate) fn router() -> Router<AppState> {
|
|||
router
|
||||
.route("/", get(index))
|
||||
.route("/login", get(oidc_login))
|
||||
.route("/logout", get(logout))
|
||||
.route("/household/select", get(household::household_selection))
|
||||
.route("/household/select/:id", get(household::select_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))
|
||||
.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