feat: Initial view controller dependency and snapshot tests.
This commit is contained in:
76
Sources/ViewControllerLive/Views/Users/UserDetail.swift
Normal file
76
Sources/ViewControllerLive/Views/Users/UserDetail.swift
Normal file
@@ -0,0 +1,76 @@
|
||||
import Dependencies
|
||||
import Elementary
|
||||
import Foundation
|
||||
import SharedModels
|
||||
|
||||
struct UserDetail: HTML, Sendable {
|
||||
@Dependency(\.dateFormatter) var dateFormatter
|
||||
|
||||
let user: User?
|
||||
|
||||
var content: some HTML {
|
||||
Float(shouldDisplay: user != nil, resetURL: .user(.index)) {
|
||||
if let user {
|
||||
form(
|
||||
.hx.post(route: .user(.get(id: user.id))),
|
||||
.hx.swap(.outerHTML),
|
||||
.hx.target(.id(.user(.row(id: user.id)))),
|
||||
.hx.on(.afterRequest, .toggleContent(.float))
|
||||
) {
|
||||
div(.class("row")) {
|
||||
makeLabel(for: "username", value: "Username:")
|
||||
input(.class("col-4"), .type(.text), .id("username"), .name("username"), .value(user.username), .required)
|
||||
makeLabel(for: "email", value: "Email:")
|
||||
input(.class("col-4"), .type(.email), .id("email"), .name("email"), .value(user.email), .required)
|
||||
}
|
||||
div(.class("row")) {
|
||||
span(.class("label col-2")) { "Created:" }
|
||||
span(.class("date col-4")) { dateFormatter.formattedDate(user.createdAt) }
|
||||
span(.class("label col-2")) { "Updated:" }
|
||||
span(.class("date col-4")) { dateFormatter.formattedDate(user.updatedAt) }
|
||||
}
|
||||
div(.class("btn-row user-buttons")) {
|
||||
button(
|
||||
.type(.submit),
|
||||
.class("btn-secondary")
|
||||
) { "Update" }
|
||||
Button.danger { "Delete" }
|
||||
.attributes(
|
||||
.hx.delete(route: .user(.get(id: user.id))),
|
||||
.hx.trigger(.event(.click)),
|
||||
.hx.swap(.outerHTML),
|
||||
.hx.target(.id(.user(.row(id: user.id)))),
|
||||
.hx.confirm("Are you sure you want to delete this user?"),
|
||||
.hx.on(
|
||||
.afterRequest,
|
||||
.toggleContent(.float), .setWindowLocation(to: .user(.index))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeLabel(
|
||||
for name: String,
|
||||
value: String
|
||||
) -> some HTML {
|
||||
label(.for(name), .class("col-2")) { span(.class("label")) { 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)
|
||||
}
|
||||
}
|
||||
119
Sources/ViewControllerLive/Views/Users/UserForm.swift
Normal file
119
Sources/ViewControllerLive/Views/Users/UserForm.swift
Normal file
@@ -0,0 +1,119 @@
|
||||
import Elementary
|
||||
import ElementaryHTMX
|
||||
import SharedModels
|
||||
|
||||
// Form used to login or create a new user.
|
||||
struct UserForm: HTML, Sendable {
|
||||
let context: Context
|
||||
|
||||
var content: some HTML {
|
||||
if context == .create {
|
||||
Float(shouldDisplay: true) {
|
||||
makeForm()
|
||||
}
|
||||
} else {
|
||||
makeForm()
|
||||
}
|
||||
}
|
||||
|
||||
private func makeForm() -> some HTML {
|
||||
form(
|
||||
.id(.user(.form)),
|
||||
.class("user-form"),
|
||||
.hx.post(context.targetURL),
|
||||
.hx.pushURL(context.pushURL),
|
||||
.hx.target(context.target),
|
||||
.hx.swap(context == .create ? .afterBegin.transition(true).swap("0.5s") : .outerHTML),
|
||||
.hx.on(
|
||||
.afterRequest,
|
||||
.ifSuccessful(.resetForm, .toggleContent(.float))
|
||||
)
|
||||
) {
|
||||
if case let .login(next) = context, let next {
|
||||
input(.type(.hidden), .name("next"), .value(next))
|
||||
}
|
||||
div(.class("row")) {
|
||||
input(.type(.text), .id("username"), .name("username"), .placeholder("Username"), .autofocus, .required)
|
||||
}
|
||||
if context.showEmailInput {
|
||||
div(.class("row")) {
|
||||
input(.type(.email), .id("email"), .name("email"), .placeholder("Email"), .required)
|
||||
}
|
||||
}
|
||||
div(.class("row")) {
|
||||
input(.type(.password), .id("password"), .name("password"), .placeholder("Password"), .required)
|
||||
}
|
||||
if context.showConfirmPassword {
|
||||
div(.class("row")) {
|
||||
input(
|
||||
.type(.password), .id("confirmPassword"), .name("confirmPassword"),
|
||||
.placeholder("Confirm Password"),
|
||||
.required
|
||||
)
|
||||
}
|
||||
}
|
||||
div(.class("row")) {
|
||||
button(.type(.submit), .class("btn-primary")) { context.buttonLabel }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Context: Equatable {
|
||||
case create
|
||||
case login(next: String?)
|
||||
|
||||
var showConfirmPassword: Bool {
|
||||
switch self {
|
||||
case .create: return true
|
||||
case .login: return false
|
||||
}
|
||||
}
|
||||
|
||||
var showEmailInput: Bool {
|
||||
switch self {
|
||||
case .create: return true
|
||||
case .login: return false
|
||||
}
|
||||
}
|
||||
|
||||
var pushURL: Bool {
|
||||
switch self {
|
||||
case .create: return false
|
||||
case .login: return true
|
||||
}
|
||||
}
|
||||
|
||||
var buttonLabel: String {
|
||||
switch self {
|
||||
case .create:
|
||||
return "Create"
|
||||
case .login:
|
||||
return "Login"
|
||||
}
|
||||
}
|
||||
|
||||
var target: HXTarget {
|
||||
switch self {
|
||||
case .create:
|
||||
return .id(.user(.table))
|
||||
case .login:
|
||||
return .body
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Return a ViewRoute.
|
||||
var targetURL: String {
|
||||
switch self {
|
||||
case .create:
|
||||
return "/users"
|
||||
case .login:
|
||||
return "/login"
|
||||
// let path = "/login"
|
||||
// if let next {
|
||||
// return "\(path)?next=\(next)"
|
||||
// }
|
||||
// return path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Sources/ViewControllerLive/Views/Users/UserTable.swift
Normal file
57
Sources/ViewControllerLive/Views/Users/UserTable.swift
Normal file
@@ -0,0 +1,57 @@
|
||||
import DatabaseClient
|
||||
import Dependencies
|
||||
import Elementary
|
||||
import ElementaryHTMX
|
||||
import SharedModels
|
||||
|
||||
struct UserTable: HTML {
|
||||
|
||||
let users: [User]
|
||||
|
||||
var content: some HTML {
|
||||
table {
|
||||
thead {
|
||||
tr {
|
||||
th { "Username" }
|
||||
th { "Email" }
|
||||
th(.style("width: 50px;")) {
|
||||
Button.add()
|
||||
.attributes(
|
||||
.hx.get(route: .user(.form)),
|
||||
.hx.target(.id(.float)),
|
||||
.hx.swap(.outerHTML)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
tbody(.id(.user(.table))) {
|
||||
for user in users {
|
||||
Row(user: user)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Row: HTML {
|
||||
let user: User
|
||||
|
||||
init(user: User) {
|
||||
self.user = user
|
||||
}
|
||||
|
||||
var content: some HTML<HTMLTag.tr> {
|
||||
tr(.id(.user(.row(id: user.id)))) {
|
||||
td { user.username }
|
||||
td { user.email }
|
||||
td {
|
||||
Button.detail().attributes(
|
||||
.hx.get(route: .user(.get(id: user.id))),
|
||||
.hx.target(.id(.float)),
|
||||
.hx.swap(.outerHTML),
|
||||
.hx.pushURL(true)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user