diff --git a/assets/tailwind.css b/assets/tailwind.css index eef4d1e..6fb3902 100644 --- a/assets/tailwind.css +++ b/assets/tailwind.css @@ -755,6 +755,20 @@ html { } @media (hover:hover) { + .label a:hover { + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + } + + .tab:hover { + --tw-text-opacity: 1; + } + + .tabs-boxed :is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):hover, .tabs-boxed :is(input:checked):hover { + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); + } + .table tr.hover:hover, .table tr.hover:nth-child(even):hover { --tw-bg-opacity: 1; @@ -838,6 +852,25 @@ html { outline-offset: 2px; } +.card-body { + display: flex; + flex: 1 1 auto; + flex-direction: column; + padding: var(--padding-card, 2rem); + gap: 0.5rem; +} + +.card-body :where(p) { + flex-grow: 1; +} + +.card-actions { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: 0.5rem; +} + .card figure { display: flex; align-items: center; @@ -1007,6 +1040,13 @@ html { --tw-scale-y: 1; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } + + .tab[disabled], + .tab[disabled]:hover { + cursor: not-allowed; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; + } } .dropdown:is(details) summary::-webkit-details-marker { @@ -1067,6 +1107,24 @@ html { animation: button-pop var(--animation-btn, 0.25s) ease-out; } +.form-control { + display: flex; + flex-direction: column; +} + +.label { + display: flex; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + align-items: center; + justify-content: space-between; + padding-left: 0.25rem; + padding-right: 0.25rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + .input { flex-shrink: 1; -webkit-appearance: none; @@ -1196,6 +1254,72 @@ html { min-width: 4rem; } +.tabs { + display: grid; + align-items: flex-end; +} + +.tabs-lifted:has(.tab-content[class^="rounded-"]) + .tab:first-child:not(:is(.tab-active, [aria-selected="true"])), .tabs-lifted:has(.tab-content[class*=" rounded-"]) + .tab:first-child:not(:is(.tab-active, [aria-selected="true"])) { + border-bottom-color: transparent; +} + +.tab { + position: relative; + grid-row-start: 1; + display: inline-flex; + height: 2rem; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + flex-wrap: wrap; + align-items: center; + justify-content: center; + text-align: center; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + --tab-padding: 1rem; + --tw-text-opacity: 0.5; + --tab-color: var(--fallback-bc,oklch(var(--bc)/1)); + --tab-bg: var(--fallback-b1,oklch(var(--b1)/1)); + --tab-border-color: var(--fallback-b3,oklch(var(--b3)/1)); + color: var(--tab-color); + padding-inline-start: var(--tab-padding, 1rem); + padding-inline-end: var(--tab-padding, 1rem); +} + +.tab:is(input[type="radio"]) { + width: auto; + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; +} + +.tab:is(input[type="radio"]):after { + --tw-content: attr(aria-label); + content: var(--tw-content); +} + +.tab:not(input):empty { + cursor: default; + grid-column-start: span 9999; +} + +:checked + .tab-content:nth-child(2), + :is(.tab-active, [aria-selected="true"]) + .tab-content:nth-child(2) { + border-start-start-radius: 0px; +} + +input.tab:checked + .tab-content, +:is(.tab-active, [aria-selected="true"]) + .tab-content { + display: block; +} + .table { position: relative; width: 100%; @@ -1239,6 +1363,11 @@ html { --tw-text-opacity: 0.2; } +.btm-nav > * .label { + font-size: 1rem; + line-height: 1.5rem; +} + @media (prefers-reduced-motion: no-preference) { .btn { animation: button-pop var(--animation-btn, 0.25s) ease-out; @@ -1472,6 +1601,13 @@ html { --tw-text-opacity: 0.2; } +.label-text { + font-size: 0.875rem; + line-height: 1.25rem; + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); +} + .input input { --tw-bg-opacity: 1; background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); @@ -1487,6 +1623,10 @@ html { line-height: 1em; } +.input-bordered { + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + .input:focus, .input:focus-within { box-shadow: none; @@ -1782,6 +1922,140 @@ html { color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity))); } +.tabs-lifted > .tab:focus-visible { + border-end-end-radius: 0; + border-end-start-radius: 0; +} + +.tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]), .tab:is(input:checked) { + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-border-opacity: 1; + --tw-text-opacity: 1; +} + +.tab:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.tab:focus-visible { + outline: 2px solid currentColor; + outline-offset: -5px; +} + +.tab-disabled, + .tab[disabled] { + cursor: not-allowed; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; +} + +.tabs-bordered > .tab { + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-border-opacity: 0.2; + border-style: solid; + border-bottom-width: calc(var(--tab-border, 1px) + 1px); +} + +.tabs-lifted > .tab { + border: var(--tab-border, 1px) solid transparent; + border-width: 0 0 var(--tab-border, 1px) 0; + border-start-start-radius: var(--tab-radius, 0.5rem); + border-start-end-radius: var(--tab-radius, 0.5rem); + border-bottom-color: var(--tab-border-color); + padding-inline-start: var(--tab-padding, 1rem); + padding-inline-end: var(--tab-padding, 1rem); + padding-top: var(--tab-border, 1px); +} + +.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]), .tabs-lifted > .tab:is(input:checked) { + background-color: var(--tab-bg); + border-width: var(--tab-border, 1px) var(--tab-border, 1px) 0 var(--tab-border, 1px); + border-inline-start-color: var(--tab-border-color); + border-inline-end-color: var(--tab-border-color); + border-top-color: var(--tab-border-color); + padding-inline-start: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); + padding-inline-end: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); + padding-bottom: var(--tab-border, 1px); + padding-top: 0; +} + +.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):before, .tabs-lifted > .tab:is(input:checked):before { + z-index: 1; + content: ""; + display: block; + position: absolute; + width: calc(100% + var(--tab-radius, 0.5rem) * 2); + height: var(--tab-radius, 0.5rem); + bottom: 0; + background-size: var(--tab-radius, 0.5rem); + background-position: top left, + top right; + background-repeat: no-repeat; + --tab-grad: calc(69% - var(--tab-border, 1px)); + --radius-start: radial-gradient( + circle at top left, + transparent var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)), + var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px) + ); + --radius-end: radial-gradient( + circle at top right, + transparent var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)), + var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px) + ); + background-image: var(--radius-start), var(--radius-end); +} + +.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):first-child:before, .tabs-lifted > .tab:is(input:checked):first-child:before { + background-image: var(--radius-end); + background-position: top right; +} + +[dir="rtl"] .tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):first-child:before, [dir="rtl"] .tabs-lifted > .tab:is(input:checked):first-child:before { + background-image: var(--radius-start); + background-position: top left; +} + +.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):last-child:before, .tabs-lifted > .tab:is(input:checked):last-child:before { + background-image: var(--radius-start); + background-position: top left; +} + +[dir="rtl"] .tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):last-child:before, [dir="rtl"] .tabs-lifted > .tab:is(input:checked):last-child:before { + background-image: var(--radius-end); + background-position: top right; +} + +.tabs-lifted + > :is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]) + + .tabs-lifted + :is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):before, .tabs-lifted > .tab:is(input:checked) + .tabs-lifted .tab:is(input:checked):before { + background-image: var(--radius-end); + background-position: top right; +} + +.tabs-boxed { + border-radius: var(--rounded-btn, 0.5rem); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + padding: 0.25rem; +} + +.tabs-boxed .tab { + border-radius: var(--rounded-btn, 0.5rem); +} + +.tabs-boxed :is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]), .tabs-boxed :is(input:checked) { + --tw-bg-opacity: 1; + background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); +} + .table:where([dir="rtl"], [dir="rtl"] *) { text-align: right; } @@ -1915,10 +2189,52 @@ html { grid-template-rows: repeat(1, minmax(0, 1fr)); } +.tabs-md :where(.tab) { + height: 2rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + --tab-padding: 1rem; +} + +.tabs-lg :where(.tab) { + height: 3rem; + font-size: 1.125rem; + line-height: 1.75rem; + line-height: 2; + --tab-padding: 1.25rem; +} + +.tabs-sm :where(.tab) { + height: 1.5rem; + font-size: 0.875rem; + line-height: .75rem; + --tab-padding: 0.75rem; +} + +.tabs-xs :where(.tab) { + height: 1.25rem; + font-size: 0.75rem; + line-height: .75rem; + --tab-padding: 0.5rem; +} + +.card-compact .card-body { + padding: 1rem; + font-size: 0.875rem; + line-height: 1.25rem; +} + .card-compact .card-title { margin-bottom: 0.25rem; } +.card-normal .card-body { + padding: var(--padding-card, 2rem); + font-size: 1rem; + line-height: 1.5rem; +} + .card-normal .card-title { margin-bottom: 0.75rem; } @@ -1999,6 +2315,10 @@ html { margin-bottom: 0.25rem; } +.mb-10 { + margin-bottom: 2.5rem; +} + .mb-5 { margin-bottom: 1.25rem; } @@ -2007,10 +2327,18 @@ html { margin-top: 2.5rem; } +.mt-20 { + margin-top: 5rem; +} + .mt-5 { margin-top: 1.25rem; } +.mt-8 { + margin-top: 2rem; +} + .flex { display: flex; } @@ -2126,6 +2454,11 @@ html { background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); } +.bg-base-300 { + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); +} + .p-5 { padding: 1.25rem; } @@ -2154,6 +2487,15 @@ html { padding-bottom: 2.5rem; } +.text-center { + text-align: center; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + .text-xl { font-size: 1.25rem; line-height: 1.75rem; @@ -2172,6 +2514,12 @@ html { color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity))); } +.shadow-xl { + --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + .filter { filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); } diff --git a/src/components/layout.rs b/src/components/layout.rs index 4e19c48..70ba716 100644 --- a/src/components/layout.rs +++ b/src/components/layout.rs @@ -1,3 +1,39 @@ +pub mod auth; pub mod icons; pub mod navbar; pub mod topbar; + +use crate::Route; +use auth::Auth; +use dioxus::prelude::*; + +#[component] +pub fn Global() -> Element { + let user = use_resource(get_user_from_cookie); + + use_context_provider(|| user); + + rsx! { + match &*user.read_unchecked() { + Some(Ok(_)) => rsx! { + crate::components::layout::topbar::Topbar {} + main { + class: "h-full overflow-y-auto", + Outlet:: {} + } + crate::components::layout::navbar::Navbar {} + }, + Some(Err(_)) => rsx! { + Auth { } + }, + None => rsx! { + div { "Loading..." } + } + } + } +} + +#[server] +async fn get_user_from_cookie() -> Result<(), ServerFnError> { + Err(ServerFnError::new("Not authenticated")) +} diff --git a/src/components/layout/auth.rs b/src/components/layout/auth.rs new file mode 100644 index 0000000..4d21119 --- /dev/null +++ b/src/components/layout/auth.rs @@ -0,0 +1,102 @@ +use dioxus::prelude::*; + +pub fn Auth() -> Element { + let mut register = use_signal(|| false); + + rsx! { + div { + class: "flex flex-col items-center", + h1 { class: "font-bold text-primary text-center text-2xl mt-20", "Waddinxveense Reddingsbrigade" }, + div { + class: "card bg-base-200 mt-20 w-full max-w-lg shadow-xl", + div { + class: "card-body", + div { + role: "tablist", + class: "tabs tabs-boxed bg-base-300 mx-auto w-full mb-10", + button { + onclick: move |_| register.set(false), + role: "tab", + class: "tab", + class: if !register() { "tab-active" }, + "Inloggen" + }, + button { + onclick: move |_| register.set(true), + role: "tab", + class: "tab", + class: if register() { "tab-active" }, + "Registreren" + }, + } + if !register() { + Login { } + } else { + Register { } + } + } + } + } + } +} + +fn Login() -> Element { + rsx! { + form { + label { + class: "form-control w-full", + div { + class: "label", + span { class: "label-text", "Email" }, + } + input { + r#type: "email", + class: "input input-bordered w-full", + }, + } + label { + class: "form-control w-full mt-5", + div { + class: "label", + span { class: "label-text", "Password" }, + } + input { + r#type: "password", + class: "input input-bordered w-full", + }, + } + div { + class: "card-actions mt-8", + button { + class: "btn btn-wide btn-primary", + "Inloggen" + } + } + } + } +} + +fn Register() -> Element { + rsx! { + form { + label { + class: "form-control w-full", + div { + class: "label", + span { class: "label-text", "Registratie code" }, + } + input { + r#type: "text", + class: "input input-bordered w-full", + }, + } + div { + class: "card-actions mt-8", + button { + class: "btn btn-primary", + "Registreren" + } + } + } + } +} diff --git a/src/main.rs b/src/main.rs index fb3b108..1b33c11 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,12 +10,13 @@ use tracing::Level; use components::admin::members::migration::Migration; use components::agenda::Agenda; use components::home::Home; +use components::layout::Global; use components::news::News; use components::settings::Settings; #[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub enum Route { - #[layout(Wrapper)] + #[layout(Global)] #[route("/")] Home {}, #[route("/agenda")] @@ -67,18 +68,6 @@ fn main() { launch(App); } -#[component] -fn Wrapper() -> Element { - rsx! { - components::layout::topbar::Topbar {} - main { - class: "h-full overflow-y-auto", - Outlet:: {} - } - components::layout::navbar::Navbar {} - } -} - fn App() -> Element { rsx! { div { diff --git a/src/util/model.rs b/src/util/model.rs index 0dd2a03..dd3fdb2 100644 --- a/src/util/model.rs +++ b/src/util/model.rs @@ -1 +1,2 @@ pub mod member; +pub mod user; diff --git a/src/util/model/user.rs b/src/util/model/user.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/util/model/user.rs @@ -0,0 +1 @@ +