feat: Initial view controller dependency and snapshot tests.

This commit is contained in:
2025-01-23 10:57:20 -05:00
parent c74433c2eb
commit 5695d0e13c
49 changed files with 2802 additions and 1 deletions

View 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)
}
}

View 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
}
}
}
}

View 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)
)
}
}
}
}
}