added ability to edit exercise
This commit is contained in:
@@ -8,6 +8,11 @@
|
|||||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||||
"Courier New", monospace;
|
"Courier New", monospace;
|
||||||
--spacing: 0.25rem;
|
--spacing: 0.25rem;
|
||||||
|
--container-2xl: 42rem;
|
||||||
|
--container-3xl: 48rem;
|
||||||
|
--container-4xl: 56rem;
|
||||||
|
--container-5xl: 64rem;
|
||||||
|
--container-7xl: 80rem;
|
||||||
--text-xs: 0.75rem;
|
--text-xs: 0.75rem;
|
||||||
--text-xs--line-height: calc(1 / 0.75);
|
--text-xs--line-height: calc(1 / 0.75);
|
||||||
--text-xl: 1.25rem;
|
--text-xl: 1.25rem;
|
||||||
@@ -16,7 +21,6 @@
|
|||||||
--text-2xl--line-height: calc(2 / 1.5);
|
--text-2xl--line-height: calc(2 / 1.5);
|
||||||
--text-3xl: 1.875rem;
|
--text-3xl: 1.875rem;
|
||||||
--text-3xl--line-height: calc(2.25 / 1.875);
|
--text-3xl--line-height: calc(2.25 / 1.875);
|
||||||
--font-weight-semibold: 600;
|
|
||||||
--font-weight-bold: 700;
|
--font-weight-bold: 700;
|
||||||
--default-font-family: var(--font-sans);
|
--default-font-family: var(--font-sans);
|
||||||
--default-mono-font-family: var(--font-mono);
|
--default-mono-font-family: var(--font-mono);
|
||||||
@@ -748,9 +752,6 @@
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.list-col-wrap {
|
|
||||||
grid-row-start: 2;
|
|
||||||
}
|
|
||||||
.label {
|
.label {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -788,6 +789,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.mt-3 {
|
||||||
|
margin-top: calc(var(--spacing) * 3);
|
||||||
|
}
|
||||||
|
.mt-auto {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
.fieldset-legend {
|
.fieldset-legend {
|
||||||
margin-bottom: calc(0.25rem * -1);
|
margin-bottom: calc(0.25rem * -1);
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -798,9 +805,15 @@
|
|||||||
color: var(--color-base-content);
|
color: var(--color-base-content);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
.mb-3 {
|
||||||
|
margin-bottom: calc(var(--spacing) * 3);
|
||||||
|
}
|
||||||
.mb-5 {
|
.mb-5 {
|
||||||
margin-bottom: calc(var(--spacing) * 5);
|
margin-bottom: calc(var(--spacing) * 5);
|
||||||
}
|
}
|
||||||
|
.ml-auto {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
.fieldset {
|
.fieldset {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: calc(0.25rem * 1.5);
|
gap: calc(0.25rem * 1.5);
|
||||||
@@ -817,10 +830,6 @@
|
|||||||
width: var(--size);
|
width: var(--size);
|
||||||
height: var(--size);
|
height: var(--size);
|
||||||
}
|
}
|
||||||
.size-\[1\.2em\] {
|
|
||||||
width: 1.2em;
|
|
||||||
height: 1.2em;
|
|
||||||
}
|
|
||||||
.size-\[1\.6em\] {
|
.size-\[1\.6em\] {
|
||||||
width: 1.6em;
|
width: 1.6em;
|
||||||
height: 1.6em;
|
height: 1.6em;
|
||||||
@@ -837,6 +846,21 @@
|
|||||||
.w-full {
|
.w-full {
|
||||||
width: 100%;
|
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 {
|
.link {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration-line: underline;
|
text-decoration-line: underline;
|
||||||
@@ -853,6 +877,9 @@
|
|||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.justify-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
.space-y-1 {
|
.space-y-1 {
|
||||||
:where(& > :not(:last-child)) {
|
:where(& > :not(:last-child)) {
|
||||||
--tw-space-y-reverse: 0;
|
--tw-space-y-reverse: 0;
|
||||||
@@ -866,23 +893,54 @@
|
|||||||
.rounded-box {
|
.rounded-box {
|
||||||
border-radius: var(--radius-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 {
|
.bg-base-100 {
|
||||||
background-color: var(--color-base-100);
|
background-color: var(--color-base-100);
|
||||||
}
|
}
|
||||||
.bg-base-200 {
|
.bg-base-200 {
|
||||||
background-color: var(--color-base-200);
|
background-color: var(--color-base-200);
|
||||||
}
|
}
|
||||||
.fill-primary-content {
|
.bg-base-300 {
|
||||||
fill: var(--color-primary-content);
|
background-color: var(--color-base-300);
|
||||||
|
}
|
||||||
|
.p-5 {
|
||||||
|
padding: calc(var(--spacing) * 5);
|
||||||
}
|
}
|
||||||
.p-10 {
|
.p-10 {
|
||||||
padding: calc(var(--spacing) * 10);
|
padding: calc(var(--spacing) * 10);
|
||||||
}
|
}
|
||||||
.py-2 {
|
.px-3 {
|
||||||
padding-block: calc(var(--spacing) * 2);
|
padding-inline: calc(var(--spacing) * 3);
|
||||||
}
|
}
|
||||||
.py-5 {
|
.px-10 {
|
||||||
padding-block: calc(var(--spacing) * 5);
|
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 {
|
.text-xs {
|
||||||
font-size: var(--text-xs);
|
font-size: var(--text-xs);
|
||||||
@@ -892,20 +950,6 @@
|
|||||||
--tw-font-weight: var(--font-weight-bold);
|
--tw-font-weight: var(--font-weight-bold);
|
||||||
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 {
|
.btn-ghost {
|
||||||
&:not(.btn-active, :hover, :active:focus, :focus-visible) {
|
&:not(.btn-active, :hover, :active:focus, :focus-visible) {
|
||||||
--btn-shadow: "";
|
--btn-shadow: "";
|
||||||
@@ -1202,94 +1246,21 @@ h3 {
|
|||||||
inherits: false;
|
inherits: false;
|
||||||
initial-value: 0;
|
initial-value: 0;
|
||||||
}
|
}
|
||||||
|
@property --tw-border-style {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: solid;
|
||||||
|
}
|
||||||
@property --tw-font-weight {
|
@property --tw-font-weight {
|
||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
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: "<percentage>";
|
|
||||||
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: "<percentage>";
|
|
||||||
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: "<length>";
|
|
||||||
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 {
|
@layer properties {
|
||||||
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
@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 {
|
*, ::before, ::after, ::backdrop {
|
||||||
--tw-space-y-reverse: 0;
|
--tw-space-y-reverse: 0;
|
||||||
|
--tw-border-style: solid;
|
||||||
--tw-font-weight: initial;
|
--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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,13 +4,16 @@ use super::empty;
|
|||||||
|
|
||||||
pub fn desktop_minimal(content: Markup, name: &str) -> Markup {
|
pub fn desktop_minimal(content: Markup, name: &str) -> Markup {
|
||||||
let content = html! {
|
let content = html! {
|
||||||
div class="w-full h-screen flex" {
|
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" {
|
div class="w-56" {
|
||||||
(sidebar())
|
(sidebar())
|
||||||
}
|
}
|
||||||
div class="w-full" {
|
div class="w-full" {
|
||||||
(content)
|
(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -19,7 +22,7 @@ pub fn desktop_minimal(content: Markup, name: &str) -> Markup {
|
|||||||
|
|
||||||
pub fn desktop(content: Markup, name: &str) -> Markup {
|
pub fn desktop(content: Markup, name: &str) -> Markup {
|
||||||
let content = html! {
|
let content = html! {
|
||||||
div class="p-10" {
|
div class="pl-10 pt-10 w-full max-w-2xl" {
|
||||||
(content)
|
(content)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -29,8 +32,11 @@ pub fn desktop(content: Markup, name: &str) -> Markup {
|
|||||||
|
|
||||||
fn sidebar() -> Markup {
|
fn sidebar() -> Markup {
|
||||||
html! {
|
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 {
|
li {
|
||||||
|
div class="pt-3 pb-6 mt-3" {
|
||||||
|
h2 { "Timo's Workouts" }
|
||||||
|
}
|
||||||
a href="/" {
|
a href="/" {
|
||||||
"Overview"
|
"Overview"
|
||||||
}
|
}
|
||||||
|
@@ -1,16 +1,19 @@
|
|||||||
use crate::{AppError, layouts, util::AppState};
|
use crate::{AppError, layouts, util::AppState};
|
||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Form, Router,
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
http::HeaderMap,
|
http::HeaderMap,
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::get,
|
routing::get,
|
||||||
};
|
};
|
||||||
use maud::{Markup, html};
|
use maud::{Markup, html};
|
||||||
|
use serde::Deserialize;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub fn routes() -> Router<AppState> {
|
pub fn routes() -> Router<AppState> {
|
||||||
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<AppState>, Path(id): Path<Uuid>) -> Result<Markup, AppError> {
|
async fn page(State(state): State<AppState>, Path(id): Path<Uuid>) -> Result<Markup, AppError> {
|
||||||
@@ -23,9 +26,14 @@ async fn page(State(state): State<AppState>, Path(id): Path<Uuid>) -> Result<Mar
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let content = html! {
|
let content = html! {
|
||||||
|
div {
|
||||||
|
div class="flex w-full" {
|
||||||
h1 { (exercise.name) }
|
h1 { (exercise.name) }
|
||||||
|
a href={"/exercises/" (exercise.exercise_id) "/edit" } class="ml-auto btn" { "Edit" }
|
||||||
|
}
|
||||||
p { (exercise.description) }
|
p { (exercise.description) }
|
||||||
button hx-delete={ "/exercises/" (exercise.exercise_id) } hx-confirm="Are you sure that you want to delete this exercise?" class="btn btn-error" { "Delete Exercise" }
|
button hx-delete={ "/exercises/" (exercise.exercise_id) } hx-confirm="Are you sure that you want to delete this exercise?" class="btn btn-error" { "Delete Exercise" }
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(layouts::desktop(content, "Exercises"))
|
Ok(layouts::desktop(content, "Exercises"))
|
||||||
@@ -48,3 +56,65 @@ async fn delete(
|
|||||||
|
|
||||||
Ok((headers, html! {}))
|
Ok((headers, html! {}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn edit(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
) -> Result<impl IntoResponse, AppError> {
|
||||||
|
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<AppState>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
Form(form): Form<FormData>,
|
||||||
|
) -> Result<impl IntoResponse, AppError> {
|
||||||
|
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! {}))
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user