feat: Mostly working user view with elementary html.

This commit is contained in:
2025-01-14 22:55:34 -05:00
parent 8842957cf3
commit cf28e52fa2
13 changed files with 297 additions and 12 deletions

View File

@@ -464,3 +464,15 @@ button.edit {
.htmx-request.htmx-indicator {
display: inline;
}
.btn-detail {
border: none;
color: grey;
text-decoration: .none;
margin-left: 10px;
}
.btn-detail:hover {
background-color: #444;
opacity: 0.8;
}

View File

@@ -1,3 +1,42 @@
import DatabaseClient
import Dependencies
import Elementary
import SharedModels
import Vapor
import VaporElementary
struct UserViewController: RouteCollection {
@Dependency(\.database.users) var users
func boot(routes: any RoutesBuilder) throws {
// let users = routes.protected.grouped("users")
let users = routes.grouped("users")
users.get(use: index)
users.group(":id") {
$0.get(use: get)
}
}
@Sendable
func index(req: Request) async throws -> HTMLResponse {
HTMLResponse {
MainPage(route: .users) {
div(.class("container")) {
UserDetail(user: nil)
UserTable()
}
}
}
}
@Sendable
func get(req: Request) async throws -> HTMLResponse {
let user = try await users.get(req.ensureIDPathComponent())
return HTMLResponse { UserDetail(user: user) }
}
}
// import Dependencies
// import Fluent
// import Vapor

View File

@@ -0,0 +1,28 @@
import Dependencies
import Foundation
public extension DependencyValues {
var dateFormatter: DateFormatter {
get { self[DateFormatter.self] }
set { self[DateFormatter.self] = newValue }
}
}
#if hasFeature(RetroactiveAttribute)
extension DateFormatter: @retroactive DependencyKey {
public static var liveValue: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .short
return formatter
}
}
#else
extension DateFormatter: DependencyKey {
public static var liveValue: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .short
return formatter
}
}
#endif

View File

@@ -14,6 +14,7 @@ struct DependenciesMiddleware: AsyncMiddleware {
try await values.yield {
try await withDependencies {
$0.database = .live(database: request.db)
$0.dateFormatter = .liveValue
} operation: {
try await next.respond(to: request)
}

View File

@@ -7,3 +7,14 @@ struct ToggleFormButton: HTML {
}
}
}
enum Button {
static func danger<C: HTML>(@HTMLBuilder body: () -> C) -> some HTML<HTMLTag.button> {
button(.class("danger")) { body() }
}
static func close(id: String) -> some HTML<HTMLTag.button> {
button(.class("btn-add"), .on(.click, "toggleContent('\(id)')")) { "x" }
}
}

View File

@@ -0,0 +1,27 @@
import Elementary
struct Float<C: HTML>: HTML {
let id: String
let body: C?
init(id: String = "float") {
self.id = id
self.body = nil
}
init(id: String = "float", @HTMLBuilder body: () -> C) {
self.id = id
self.body = body()
}
var content: some HTML {
div(.id(id), .class("float")) {
if let body {
body
}
}
}
}
extension Float: Sendable where C: Sendable {}

View File

@@ -7,9 +7,15 @@ struct MainPage<Inner: HTML>: HTMLDocument {
let inner: Inner
let displayNav: Bool
let routeHeader: RouteHeaderView
init(displayNav: Bool = false, _ inner: () -> Inner) {
init(
displayNav: Bool = false,
route: ViewRoute,
_ inner: () -> Inner
) {
self.displayNav = displayNav
self.routeHeader = .init(route: route)
self.inner = inner()
}
@@ -27,6 +33,7 @@ struct MainPage<Inner: HTML>: HTMLDocument {
Navbar()
}
}
routeHeader
inner
}
}

View File

@@ -0,0 +1,26 @@
import Elementary
import ElementaryHTMX
struct RouteHeaderView: HTML {
let title: String
let description: String
init(title: String, description: String) {
self.title = title
self.description = description
}
init(route: ViewRoute) {
self.init(title: route.title, description: route.description)
}
var content: some HTML {
div(.class("container"), .style("padding: 20px 20px;")) {
h1 { title }
br()
p { description }
br()
}
}
}

View File

@@ -0,0 +1,73 @@
import Dependencies
import Elementary
import Foundation
import SharedModels
struct UserDetail: HTML, Sendable {
@Dependency(\.dateFormatter) var dateFormatter
let user: User?
var classString: String {
user != nil ? "float" : ""
}
var display: String {
user != nil ? "block" : "hidden"
}
var content: some HTML {
div(
.id("float"),
.class(classString),
.style("display: \(display);")
) {
if let user {
Button.close(id: "float")
form {
div(.class("row")) {
makeLabel(for: "username", value: "Username:")
input(.type(.text), .name("username"), .value(user.username))
makeLabel(for: "email", value: "Email:")
input(.type(.email), .name("email"), .value(user.username))
}
div(.class("row")) {
div(.style("display: inline-block;")) {
h3(.class("label")) { "Created:" }
h3 { dateFormatter.formattedDate(user.createdAt) }
}
div(.style("display: inline-block;")) {
h3(.class("label")) { "Updated:" }
h3 { dateFormatter.formattedDate(user.updatedAt) }
}
}
}
div(.class("btn-row user-buttons")) {
Button.danger { "Delete" }
}
}
}
}
func makeLabel(
for name: String,
value: String
) -> some HTML {
label(.for(name)) { h3 { value } }
}
func row(_ label: String, _ value: String) -> some HTML<HTMLTag.tr> {
tr {
td(.class("label")) { h3 { label } }
td { h3 { value } }
}
}
}
extension DateFormatter {
func formattedDate(_ date: Date?) -> String {
guard let date else { return "" }
return string(from: date)
}
}

View File

@@ -0,0 +1,19 @@
import DatabaseClient
import Elementary
import SharedModels
struct UserIndex: HTML {
let user: User?
init(user: User? = nil) {
self.user = user
}
var content: some HTML {
div {
// UserDetail(user: user)
div(.id("float"), .class("float")) {}
UserTable()
}
}
}

View File

@@ -14,7 +14,7 @@ struct UserTable: HTML {
tr {
th { "Username" }
th { "Email" }
th { ToggleFormButton() }
th(.style("width: 50px;")) { ToggleFormButton() }
}
}
tbody {
@@ -33,7 +33,16 @@ struct UserTable: HTML {
tr {
td { user.username }
td { user.email }
td { "Fix me." }
td {
button(
.hx.get("/users/\(user.id.uuidString)"),
.hx.target("#float"),
.hx.swap(.outerHTML),
.class("btn-detail")
) {
""
}
}
}
}
}

View File

@@ -0,0 +1,31 @@
enum ViewRoute: String {
case employees
case login
case purchaseOrders
case users
case vendors
var title: String {
switch self {
case .purchaseOrders:
return "Purchase Orders"
default:
return rawValue.capitalized
}
}
var description: String {
switch self {
case .employees:
return "Employees are who purchase orders can be issued to."
case .purchaseOrders, .login:
return ""
case .users:
return "Users are who can login and issue purchase orders for employees."
case .vendors:
return "Vendors are where purchase orders can be issued to."
}
}
}

View File

@@ -7,11 +7,12 @@ import VaporElementary
func routes(_ app: Application) throws {
try app.register(collection: ApiController())
try app.register(collection: UserViewController())
// try app.register(collection: ViewController())
app.get("test") { _ in
HTMLResponse {
MainPage(displayNav: false) {
MainPage(displayNav: false, route: .purchaseOrders) {
div(.class("container")) {
h1 { "iT WORKS" }
}
@@ -21,17 +22,18 @@ func routes(_ app: Application) throws {
app.get("login") { _ in
HTMLResponse {
MainPage(displayNav: false) {
MainPage(displayNav: false, route: .login) {
UserForm(context: .login(next: nil))
}
}
}
app.get("users") { _ in
HTMLResponse {
MainPage(displayNav: false) {
UserTable()
}
}
}
// app.get("users") { _ in
// HTMLResponse {
// // UserIndex()
// MainPage(displayNav: false, route: .users) {
// UserTable()
// }
// }
// }
}