From 9a8cd2ed864af245b61af1586b776fb06e2ec159 Mon Sep 17 00:00:00 2001 From: Timo Boomers Date: Wed, 9 Jul 2025 22:53:18 +0200 Subject: [PATCH] added ability to edit exercise --- assets/css/main.css | 185 ++++++++++++++++---------------------- src/layouts/desktop.rs | 22 +++-- src/pages/exercises/id.rs | 80 +++++++++++++++-- 3 files changed, 167 insertions(+), 120 deletions(-) diff --git a/assets/css/main.css b/assets/css/main.css index a26f181..c48b3ce 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -8,6 +8,11 @@ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --spacing: 0.25rem; + --container-2xl: 42rem; + --container-3xl: 48rem; + --container-4xl: 56rem; + --container-5xl: 64rem; + --container-7xl: 80rem; --text-xs: 0.75rem; --text-xs--line-height: calc(1 / 0.75); --text-xl: 1.25rem; @@ -16,7 +21,6 @@ --text-2xl--line-height: calc(2 / 1.5); --text-3xl: 1.875rem; --text-3xl--line-height: calc(2.25 / 1.875); - --font-weight-semibold: 600; --font-weight-bold: 700; --default-font-family: var(--font-sans); --default-mono-font-family: var(--font-mono); @@ -748,9 +752,6 @@ cursor: not-allowed; } } - .list-col-wrap { - grid-row-start: 2; - } .label { display: inline-flex; align-items: center; @@ -788,6 +789,12 @@ } } } + .mt-3 { + margin-top: calc(var(--spacing) * 3); + } + .mt-auto { + margin-top: auto; + } .fieldset-legend { margin-bottom: calc(0.25rem * -1); display: flex; @@ -798,9 +805,15 @@ color: var(--color-base-content); font-weight: 600; } + .mb-3 { + margin-bottom: calc(var(--spacing) * 3); + } .mb-5 { margin-bottom: calc(var(--spacing) * 5); } + .ml-auto { + margin-left: auto; + } .fieldset { display: grid; gap: calc(0.25rem * 1.5); @@ -817,10 +830,6 @@ width: var(--size); height: var(--size); } - .size-\[1\.2em\] { - width: 1.2em; - height: 1.2em; - } .size-\[1\.6em\] { width: 1.6em; height: 1.6em; @@ -837,6 +846,21 @@ .w-full { width: 100%; } + .max-w-2xl { + max-width: var(--container-2xl); + } + .max-w-3xl { + max-width: var(--container-3xl); + } + .max-w-4xl { + max-width: var(--container-4xl); + } + .max-w-5xl { + max-width: var(--container-5xl); + } + .max-w-7xl { + max-width: var(--container-7xl); + } .link { cursor: pointer; text-decoration-line: underline; @@ -853,6 +877,9 @@ outline-offset: 2px; } } + .justify-center { + justify-content: center; + } .space-y-1 { :where(& > :not(:last-child)) { --tw-space-y-reverse: 0; @@ -866,23 +893,54 @@ .rounded-box { border-radius: var(--radius-box); } + .border-r { + border-right-style: var(--tw-border-style); + border-right-width: 1px; + } + .border-base-200 { + border-color: var(--color-base-200); + } + .border-base-300 { + border-color: var(--color-base-300); + } + .border-base-content { + border-color: var(--color-base-content); + } .bg-base-100 { background-color: var(--color-base-100); } .bg-base-200 { background-color: var(--color-base-200); } - .fill-primary-content { - fill: var(--color-primary-content); + .bg-base-300 { + background-color: var(--color-base-300); + } + .p-5 { + padding: calc(var(--spacing) * 5); } .p-10 { padding: calc(var(--spacing) * 10); } - .py-2 { - padding-block: calc(var(--spacing) * 2); + .px-3 { + padding-inline: calc(var(--spacing) * 3); } - .py-5 { - padding-block: calc(var(--spacing) * 5); + .px-10 { + padding-inline: calc(var(--spacing) * 10); + } + .py-3 { + padding-block: calc(var(--spacing) * 3); + } + .pt-3 { + padding-top: calc(var(--spacing) * 3); + } + .pt-10 { + padding-top: calc(var(--spacing) * 10); + } + .pb-6 { + padding-bottom: calc(var(--spacing) * 6); + } + .pl-10 { + padding-left: calc(var(--spacing) * 10); } .text-xs { font-size: var(--text-xs); @@ -892,20 +950,6 @@ --tw-font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold); } - .font-semibold { - --tw-font-weight: var(--font-weight-semibold); - font-weight: var(--font-weight-semibold); - } - .text-primary-content { - color: var(--color-primary-content); - } - .opacity-60 { - opacity: 60%; - } - .shadow-md { - --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); - box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); - } .btn-ghost { &:not(.btn-active, :hover, :active:focus, :focus-visible) { --btn-shadow: ""; @@ -1202,94 +1246,21 @@ h3 { inherits: false; initial-value: 0; } +@property --tw-border-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} @property --tw-font-weight { syntax: "*"; inherits: false; } -@property --tw-shadow { - syntax: "*"; - inherits: false; - initial-value: 0 0 #0000; -} -@property --tw-shadow-color { - syntax: "*"; - inherits: false; -} -@property --tw-shadow-alpha { - syntax: ""; - inherits: false; - initial-value: 100%; -} -@property --tw-inset-shadow { - syntax: "*"; - inherits: false; - initial-value: 0 0 #0000; -} -@property --tw-inset-shadow-color { - syntax: "*"; - inherits: false; -} -@property --tw-inset-shadow-alpha { - syntax: ""; - inherits: false; - initial-value: 100%; -} -@property --tw-ring-color { - syntax: "*"; - inherits: false; -} -@property --tw-ring-shadow { - syntax: "*"; - inherits: false; - initial-value: 0 0 #0000; -} -@property --tw-inset-ring-color { - syntax: "*"; - inherits: false; -} -@property --tw-inset-ring-shadow { - syntax: "*"; - inherits: false; - initial-value: 0 0 #0000; -} -@property --tw-ring-inset { - syntax: "*"; - inherits: false; -} -@property --tw-ring-offset-width { - syntax: ""; - inherits: false; - initial-value: 0px; -} -@property --tw-ring-offset-color { - syntax: "*"; - inherits: false; - initial-value: #fff; -} -@property --tw-ring-offset-shadow { - syntax: "*"; - inherits: false; - initial-value: 0 0 #0000; -} @layer properties { @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { *, ::before, ::after, ::backdrop { --tw-space-y-reverse: 0; + --tw-border-style: solid; --tw-font-weight: initial; - --tw-shadow: 0 0 #0000; - --tw-shadow-color: initial; - --tw-shadow-alpha: 100%; - --tw-inset-shadow: 0 0 #0000; - --tw-inset-shadow-color: initial; - --tw-inset-shadow-alpha: 100%; - --tw-ring-color: initial; - --tw-ring-shadow: 0 0 #0000; - --tw-inset-ring-color: initial; - --tw-inset-ring-shadow: 0 0 #0000; - --tw-ring-inset: initial; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-offset-shadow: 0 0 #0000; } } } diff --git a/src/layouts/desktop.rs b/src/layouts/desktop.rs index 0858049..41ca40f 100644 --- a/src/layouts/desktop.rs +++ b/src/layouts/desktop.rs @@ -4,12 +4,15 @@ use super::empty; pub fn desktop_minimal(content: Markup, name: &str) -> Markup { let content = html! { - div class="w-full h-screen flex" { - div class="w-56" { - (sidebar()) - } - div class="w-full" { - (content) + div class="w-full h-screen flex justify-center bg-base-200" { + div class="w-full max-w-7xl flex bg-base-100" { + div class="w-56" { + (sidebar()) + } + div class="w-full" { + (content) + } + } } }; @@ -19,7 +22,7 @@ pub fn desktop_minimal(content: Markup, name: &str) -> Markup { pub fn desktop(content: Markup, name: &str) -> Markup { let content = html! { - div class="p-10" { + div class="pl-10 pt-10 w-full max-w-2xl" { (content) } }; @@ -29,8 +32,11 @@ pub fn desktop(content: Markup, name: &str) -> Markup { fn sidebar() -> Markup { html! { - ul class="menu bg-base-200 rounded-box w-full h-full" { + ul class="menu w-full h-full border-r border-base-300" { li { + div class="pt-3 pb-6 mt-3" { + h2 { "Timo's Workouts" } + } a href="/" { "Overview" } diff --git a/src/pages/exercises/id.rs b/src/pages/exercises/id.rs index 833a0b6..a83fce6 100644 --- a/src/pages/exercises/id.rs +++ b/src/pages/exercises/id.rs @@ -1,16 +1,19 @@ use crate::{AppError, layouts, util::AppState}; use axum::{ - Router, + Form, Router, extract::{Path, State}, http::HeaderMap, response::IntoResponse, routing::get, }; use maud::{Markup, html}; +use serde::Deserialize; use uuid::Uuid; pub fn routes() -> Router { - Router::new().route("/", get(page).delete(delete)) + Router::new() + .route("/", get(page).delete(delete).put(submit_edit)) + .route("/edit", get(edit)) } async fn page(State(state): State, Path(id): Path) -> Result { @@ -23,9 +26,14 @@ async fn page(State(state): State, Path(id): Path) -> Result, + Path(id): Path, +) -> Result { + let exercise = sqlx::query_as!( + crate::models::Exercise, + "SELECT exercise_id, name, description FROM exercises WHERE exercise_id = $1", + id + ) + .fetch_one(&state.pool) + .await?; + + let content = html! { + h1 class="mb-5" { "Edit Exercise" } + + form hx-put={"/exercises/" (exercise.exercise_id)} class="space-y-1" { + fieldset class="fieldset" { + legend class="fieldset-legend" { "Name" } + input required="true" name="name" class="input" value={(exercise.name)} {} + } + + fieldset class="fieldset" { + legend class="fieldset-legend" { "Description" } + textarea required="true" name="description" class="textarea" { (exercise.description) } + } + + input type="submit" class="btn" value="save" { } + } + }; + + Ok(layouts::desktop(content, "Edit Exercises")) +} + +#[derive(Deserialize, Debug)] +struct FormData { + name: String, + description: String, +} + +async fn submit_edit( + State(state): State, + Path(id): Path, + Form(form): Form, +) -> Result { + sqlx::query_as!( + crate::models::Exercise, + "UPDATE exercises SET name = $1, description = $2 WHERE exercise_id = $3", + form.name, + form.description, + id + ) + .execute(&state.pool) + .await?; + + let mut headers = HeaderMap::new(); + + let location = format!("/exercises/{}", id); + headers.insert("hx-location", location.parse().unwrap()); + + Ok((headers, html! {})) +}