feat: Mostly working user view with elementary html.
This commit is contained in:
@@ -464,3 +464,15 @@ button.edit {
|
|||||||
.htmx-request.htmx-indicator {
|
.htmx-request.htmx-indicator {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-detail {
|
||||||
|
border: none;
|
||||||
|
color: grey;
|
||||||
|
text-decoration: .none;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-detail:hover {
|
||||||
|
background-color: #444;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 Dependencies
|
||||||
// import Fluent
|
// import Fluent
|
||||||
// import Vapor
|
// import Vapor
|
||||||
|
|||||||
28
Sources/App/Dependencies/DateFormatter.swift
Normal file
28
Sources/App/Dependencies/DateFormatter.swift
Normal 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
|
||||||
@@ -14,6 +14,7 @@ struct DependenciesMiddleware: AsyncMiddleware {
|
|||||||
try await values.yield {
|
try await values.yield {
|
||||||
try await withDependencies {
|
try await withDependencies {
|
||||||
$0.database = .live(database: request.db)
|
$0.database = .live(database: request.db)
|
||||||
|
$0.dateFormatter = .liveValue
|
||||||
} operation: {
|
} operation: {
|
||||||
try await next.respond(to: request)
|
try await next.respond(to: request)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
27
Sources/App/Views/Float.swift
Normal file
27
Sources/App/Views/Float.swift
Normal 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 {}
|
||||||
@@ -7,9 +7,15 @@ struct MainPage<Inner: HTML>: HTMLDocument {
|
|||||||
|
|
||||||
let inner: Inner
|
let inner: Inner
|
||||||
let displayNav: Bool
|
let displayNav: Bool
|
||||||
|
let routeHeader: RouteHeaderView
|
||||||
|
|
||||||
init(displayNav: Bool = false, _ inner: () -> Inner) {
|
init(
|
||||||
|
displayNav: Bool = false,
|
||||||
|
route: ViewRoute,
|
||||||
|
_ inner: () -> Inner
|
||||||
|
) {
|
||||||
self.displayNav = displayNav
|
self.displayNav = displayNav
|
||||||
|
self.routeHeader = .init(route: route)
|
||||||
self.inner = inner()
|
self.inner = inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +33,7 @@ struct MainPage<Inner: HTML>: HTMLDocument {
|
|||||||
Navbar()
|
Navbar()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
routeHeader
|
||||||
inner
|
inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
Sources/App/Views/RouteHeaderView.swift
Normal file
26
Sources/App/Views/RouteHeaderView.swift
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
Sources/App/Views/Users/UserDetail.swift
Normal file
73
Sources/App/Views/Users/UserDetail.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Sources/App/Views/Users/UserIndex.swift
Normal file
19
Sources/App/Views/Users/UserIndex.swift
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ struct UserTable: HTML {
|
|||||||
tr {
|
tr {
|
||||||
th { "Username" }
|
th { "Username" }
|
||||||
th { "Email" }
|
th { "Email" }
|
||||||
th { ToggleFormButton() }
|
th(.style("width: 50px;")) { ToggleFormButton() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tbody {
|
tbody {
|
||||||
@@ -33,7 +33,16 @@ struct UserTable: HTML {
|
|||||||
tr {
|
tr {
|
||||||
td { user.username }
|
td { user.username }
|
||||||
td { user.email }
|
td { user.email }
|
||||||
td { "Fix me." }
|
td {
|
||||||
|
button(
|
||||||
|
.hx.get("/users/\(user.id.uuidString)"),
|
||||||
|
.hx.target("#float"),
|
||||||
|
.hx.swap(.outerHTML),
|
||||||
|
.class("btn-detail")
|
||||||
|
) {
|
||||||
|
"〉"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
Sources/App/Views/ViewRoute.swift
Normal file
31
Sources/App/Views/ViewRoute.swift
Normal 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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,11 +7,12 @@ import VaporElementary
|
|||||||
|
|
||||||
func routes(_ app: Application) throws {
|
func routes(_ app: Application) throws {
|
||||||
try app.register(collection: ApiController())
|
try app.register(collection: ApiController())
|
||||||
|
try app.register(collection: UserViewController())
|
||||||
// try app.register(collection: ViewController())
|
// try app.register(collection: ViewController())
|
||||||
|
|
||||||
app.get("test") { _ in
|
app.get("test") { _ in
|
||||||
HTMLResponse {
|
HTMLResponse {
|
||||||
MainPage(displayNav: false) {
|
MainPage(displayNav: false, route: .purchaseOrders) {
|
||||||
div(.class("container")) {
|
div(.class("container")) {
|
||||||
h1 { "iT WORKS" }
|
h1 { "iT WORKS" }
|
||||||
}
|
}
|
||||||
@@ -21,17 +22,18 @@ func routes(_ app: Application) throws {
|
|||||||
|
|
||||||
app.get("login") { _ in
|
app.get("login") { _ in
|
||||||
HTMLResponse {
|
HTMLResponse {
|
||||||
MainPage(displayNav: false) {
|
MainPage(displayNav: false, route: .login) {
|
||||||
UserForm(context: .login(next: nil))
|
UserForm(context: .login(next: nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get("users") { _ in
|
// app.get("users") { _ in
|
||||||
HTMLResponse {
|
// HTMLResponse {
|
||||||
MainPage(displayNav: false) {
|
// // UserIndex()
|
||||||
UserTable()
|
// MainPage(displayNav: false, route: .users) {
|
||||||
}
|
// UserTable()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user