WIP: Begins work on login / signup, adds user database models, authentication needs implemented.

This commit is contained in:
2026-01-02 17:00:50 -05:00
parent 4750842a57
commit 6602c4a8b5
13 changed files with 819 additions and 85 deletions

View File

@@ -5,9 +5,15 @@ public struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable
public var lang: String { "en" }
let inner: Inner
let activeTab: Sidebar.ActiveTab
let showSidebar: Bool
init(active activeTab: Sidebar.ActiveTab, _ inner: () -> Inner) {
init(
active activeTab: Sidebar.ActiveTab,
showSidebar: Bool = true,
_ inner: () -> Inner
) {
self.activeTab = activeTab
self.showSidebar = showSidebar
self.inner = inner()
}
@@ -24,7 +30,9 @@ public struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable
// div(.class("bg-white dark:bg-gray-800 dark:text-white")) {
div {
div(.class("flex flex-row")) {
Sidebar(active: activeTab)
if showSidebar {
Sidebar(active: activeTab)
}
main(.class("flex flex-col h-screen w-full px-6 py-10")) {
inner
}

View File

@@ -9,9 +9,9 @@ struct RoomsView: HTML, Sendable {
let rooms: [Room]
var body: some HTML {
div(.class("m-10")) {
div {
Row {
h1(.class("text-3xl font-bold pb-6")) { "Room Loads" }
h1(.class("text-2xl font-bold")) { "Room Loads" }
div(
.class("tooltip tooltip-left"),
.data("tip", value: "Add room")
@@ -20,94 +20,67 @@ struct RoomsView: HTML, Sendable {
.hx.get(route: .room(.form(dismiss: false))),
.hx.target("#roomForm"),
.hx.swap(.outerHTML),
.class("btn btn-primary w-[40px]")
.class("btn btn-primary w-[40px] text-2xl")
) {
"+"
}
}
}
.attributes(.class("pb-6"))
div(
.id("roomTable"),
.class(
"""
border border-gray-200 rounded-lg shadow-lg
grid grid-cols-5 p-4
"""
)
) {
// Header
Label("Name")
// Pushes items to right
Row {
div {}
Label("Heating Load")
}
Row {
div {}
Label("Cooling Total")
}
Row {
div {}
Label("Cooling Sensible")
}
Row {
div {}
Label("Register Count")
}
// Divider
div(.class("border-b border-gray-200 col-span-5 mb-2")) {}
// Rows
for row in rooms {
span { row.name }
// Pushes items to right
Row {
div {}
Number(row.heatingLoad)
.attributes(.class("text-red-500"))
div(.class("overflow-x-auto rounded-box border")) {
table(.class("table table-zebra"), .id("roomsTable")) {
thead {
tr {
th { Label("Name") }
th { Label("Heating Load") }
th { Label("Cooling Total") }
th { Label("Cooling Sensible") }
th { Label("Register Count") }
}
}
Row {
div {}
Number(row.coolingLoad.total)
.attributes(.class("text-green-400"))
tbody {
for room in rooms {
tr {
td { room.name }
td {
Number(room.heatingLoad)
.attributes(.class("text-error"))
}
td {
Number(room.coolingLoad.total)
.attributes(.class("text-success"))
}
td {
Number(room.coolingLoad.sensible)
.attributes(.class("text-info"))
}
td {
Number(room.registerCount)
}
}
}
// TOTALS
tr(.class("font-bold text-xl")) {
td { Label("Total") }
td {
Number(rooms.heatingTotal)
.attributes(.class("badge badge-outline badge-error badge-xl"))
}
td {
Number(rooms.coolingTotal)
.attributes(
.class("badge badge-outline badge-success badge-xl"))
}
td {
Number(rooms.coolingSensibleTotal)
.attributes(.class("badge badge-outline badge-info badge-xl"))
}
td {}
}
}
Row {
div {}
Number(row.coolingLoad.sensible)
.attributes(.class("text-blue-400"))
}
Row {
div {}
Number(row.registerCount)
}
// Divider
div(.class("border-b border-gray-200 col-span-5 mb-2")) {}
}
// Totals
Label("Total")
Row {
div {}
Number(rooms.heatingTotal)
.attributes(.class("badge badge-outline badge-error badge-xl text-xl font-bold"))
}
Row {
div {}
Number(rooms.coolingTotal)
.attributes(.class("badge badge-outline badge-success badge-xl text-xl font-bold"))
}
Row {
div {}
Number(rooms.coolingSensibleTotal)
.attributes(.class("badge badge-outline badge-info badge-xl text-xl font-bold"))
}
// Empty register count column
div {}
}
RoomForm(dismiss: true)
}
}

View File

@@ -2,7 +2,7 @@ import Elementary
import ManualDCore
import Styleguide
// TODO: Need to add active to sidebar links.
// TODO: Update to use DaisyUI drawer.
struct Sidebar: HTML {
let active: ActiveTab
@@ -23,7 +23,7 @@ struct Sidebar: HTML {
Label("Theme")
input(.type(.checkbox), .class("toggle theme-controller"), .value("light"))
}
.attributes(.class("py-4"))
.attributes(.class("p-4"))
row(title: "Project", icon: .mapPin, route: .project(.index))
.attributes(.data("active", value: active == .projects ? "true" : "false"))

View File

@@ -0,0 +1,158 @@
import Elementary
import ElementaryHTMX
import Styleguide
struct LoginForm: HTML, Sendable {
let style: Style
init(style: Style = .login) {
self.style = style
}
var body: some HTML {
form {
fieldset(.class("fieldset bg-base-200 border-base-300 rounded-box w-xl border p-4")) {
legend(.class("fieldset-legend")) { style.title }
if style == .signup {
label(.class("input validator w-full")) {
SVG(.user)
input(
.type(.text), .required, .placeholder("Username"),
.minlength("3"), .pattern(.username)
)
}
div(.class("validator-hint hidden")) {
"Enter valid username"
br()
"Must be at least 3 characters"
}
}
label(.class("input validator w-full")) {
SVG(.email)
input(
.type(.email), .placeholder("Email"), .required
)
}
div(.class("validator-hint hidden")) { "Enter valid email address." }
label(.class("input validator w-full")) {
SVG(.key)
input(
.type(.password), .placeholder("Password"), .required,
.pattern(.password), .minlength("8")
)
}
if style == .signup {
label(.class("input validator w-full")) {
SVG(.key)
input(
.type(.password), .placeholder("Confirm Password"), .required,
.pattern(.password), .minlength("8")
)
}
}
div(.class("validator-hint hidden")) {
p {
"Must be more than 8 characters, including"
br()
"At least one number"
br()
"At least one lowercase letter"
br()
"At least one uppercase letter"
}
}
button(.class("btn btn-neutral mt-4")) { style.title }
}
}
// div(.class("flex items-center justify-center")) {
// div(.class("w-full mx-auto")) {
// h1(.class("text-2xl font-bold")) { style.title }
// form(
// .class("w-full h-screen")
// ) {
// fieldset(.class("fieldset w-full")) {
// legend(.class("fieldset-legend")) { "Email" }
// label(.class("input validator")) {
// SVG(.email)
// input(
// .type(.email), .placeholder("mail@site.com"), .required,
// .autofocus
// )
// }
// div(.class("validator-hint hidden")) { "Enter valid email address." }
// }
//
// if style == .signup {
// fieldset(.class("fieldset")) {
// legend(.class("fieldset-legend")) { "Name" }
// label(.class("input validator")) {
// input(
// .type(.text), .placeholder("Username"), .required,
// .init(name: "pattern", value: "[A-Za-z][A-Za-z0-9\\-]*"),
// .init(name: "minlength", value: "3")
// )
// }
// div(.class("validator-hint hidden")) { "Enter valid email address." }
// }
// }
//
// fieldset(.class("fieldset")) {
// legend(.class("fieldset-legend")) { "Password" }
// label(.class("input validator")) {
// SVG(.key)
// input(
// .type(.password), .placeholder("Password"), .required,
// .init(name: "pattern", value: "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"),
// .init(name: "minlength", value: "8")
// )
// }
// if style == .signup {
// label(.class("input validator")) {
// SVG(.key)
// input(
// .type(.password), .placeholder("Confirm Password"), .required,
// .init(name: "pattern", value: "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"),
// .init(name: "minlength", value: "8")
// )
// }
// }
// div(.class("validator-hint hidden")) {
// p {
// "Must be more than 8 characters, including"
// br()
// "At least one number"
// br()
// "At least one lowercase letter"
// br()
// "At least one uppercase letter"
// }
// }
// }
//
// SubmitButton(title: style.title)
// }
// }
// }
}
}
extension LoginForm {
enum Style: Equatable, Sendable {
case login
case signup
var title: String {
switch self {
case .login: return "Login"
case .signup: return "Sign Up"
}
}
}
}