WIP: Begins updating signup route to also setup a user's profile.
This commit is contained in:
@@ -57,7 +57,7 @@ public struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable
|
||||
div(.class("h-screen w-full")) {
|
||||
inner
|
||||
}
|
||||
.attributes(.data("theme", value: theme!.rawValue), when: theme != nil)
|
||||
.attributes(.data("theme", value: theme?.rawValue ?? "default"), when: theme != nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,34 +42,40 @@ struct Navbar: HTML, Sendable {
|
||||
}
|
||||
}
|
||||
div(.class("flex-none")) {
|
||||
details(.class("dropdown dropdown-left dropdown-bottom")) {
|
||||
summary(.class("btn w-fit px-4 py-2")) {
|
||||
SVG(.circleUser)
|
||||
}
|
||||
.navButton()
|
||||
|
||||
ul(
|
||||
.class(
|
||||
"""
|
||||
menu dropdown-content bg-base-100
|
||||
rounded-box z-1 w-fit p-2 shadow-sm
|
||||
"""
|
||||
)
|
||||
) {
|
||||
li(.class("w-full")) {
|
||||
// TODO: Save theme to user profile ??
|
||||
div(.class("flex justify-between p-4 space-x-6")) {
|
||||
Label("Theme")
|
||||
input(.type(.checkbox), .class("toggle theme-controller"), .value("light"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// button(.class("w-fit px-4 py-2")) {
|
||||
// SVG(.circleUser)
|
||||
// }
|
||||
// .navButton()
|
||||
a(
|
||||
.href(route: .user(.profile(.index))),
|
||||
) {
|
||||
SVG(.circleUser)
|
||||
}
|
||||
.navButton()
|
||||
// details(.class("dropdown dropdown-left dropdown-bottom")) {
|
||||
// summary(.class("btn w-fit px-4 py-2")) {
|
||||
// SVG(.circleUser)
|
||||
// }
|
||||
// .navButton()
|
||||
//
|
||||
// ul(
|
||||
// .class(
|
||||
// """
|
||||
// menu dropdown-content bg-base-100
|
||||
// rounded-box z-1 w-fit p-2 shadow-sm
|
||||
// """
|
||||
// )
|
||||
// ) {
|
||||
// li(.class("w-full")) {
|
||||
// // TODO: Save theme to user profile ??
|
||||
// div(.class("flex justify-between p-4 space-x-6")) {
|
||||
// Label("Theme")
|
||||
// input(.type(.checkbox), .class("toggle theme-controller"), .value("light"))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // button(.class("w-fit px-4 py-2")) {
|
||||
// // SVG(.circleUser)
|
||||
// // }
|
||||
// // .navButton()
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,10 @@
|
||||
import Dependencies
|
||||
import Elementary
|
||||
import Foundation
|
||||
import ManualDCore
|
||||
|
||||
struct TestPage: HTML, Sendable {
|
||||
var body: some HTML {
|
||||
HTMLRaw(
|
||||
"""
|
||||
<div class="drawer lg:drawer-open">
|
||||
<input id="my-drawer-4" type="checkbox" class="drawer-toggle" checked/>
|
||||
<div class="drawer-content">
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar w-full bg-base-300">
|
||||
<label for="my-drawer-4" aria-label="open sidebar" class="btn btn-square btn-ghost">
|
||||
<!-- Sidebar toggle icon -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-linejoin="round" stroke-linecap="round" stroke-width="2" fill="none" stroke="currentColor" class="my-1.5 inline-block size-4"><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path><path d="M9 4v16"></path><path d="M14 10l2 2l-2 2"></path></svg>
|
||||
</label>
|
||||
<div class="px-4">Navbar Title</div>
|
||||
</nav>
|
||||
<!-- Page content here -->
|
||||
<div class="p-4">Page Content</div>
|
||||
</div>
|
||||
|
||||
<div class="drawer-side is-drawer-close:overflow-visible">
|
||||
<label for="my-drawer-4" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||
<div class="flex min-h-full flex-col items-start bg-base-200 is-drawer-close:w-14 is-drawer-open:w-64">
|
||||
<!-- Sidebar content here -->
|
||||
<ul class="menu w-full grow">
|
||||
<!-- List item -->
|
||||
<li>
|
||||
<button class="is-drawer-close:tooltip is-drawer-close:tooltip-right" data-tip="Homepage">
|
||||
<!-- Home icon -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-linejoin="round" stroke-linecap="round" stroke-width="2" fill="none" stroke="currentColor" class="my-1.5 inline-block size-4"><path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"></path><path d="M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path></svg>
|
||||
<span class="is-drawer-close:hidden">Homepage</span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<!-- List item -->
|
||||
<li>
|
||||
<button class="is-drawer-close:tooltip is-drawer-close:tooltip-right" data-tip="Settings">
|
||||
<!-- Settings icon -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-linejoin="round" stroke-linecap="round" stroke-width="2" fill="none" stroke="currentColor" class="my-1.5 inline-block size-4"><path d="M20 7h-9"></path><path d="M14 17H5"></path><circle cx="17" cy="17" r="3"></circle><circle cx="7" cy="7" r="3"></circle></svg>
|
||||
<span class="is-drawer-close:hidden">Settings</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
UserProfileForm(userID: UUID(0), profile: nil, dismiss: false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,25 +25,6 @@ struct LoginForm: HTML, Sendable {
|
||||
input(.class("hidden"), .name("next"), .value(next))
|
||||
}
|
||||
|
||||
if style == .signup {
|
||||
div {
|
||||
label(.class("input validator w-full")) {
|
||||
SVG(.user)
|
||||
input(
|
||||
.type(.text), .required, .placeholder("Username"),
|
||||
.name("username"), .id("username"),
|
||||
.minlength("3"), .pattern(.username),
|
||||
.autofocus
|
||||
)
|
||||
}
|
||||
div(.class("validator-hint hidden")) {
|
||||
"Enter valid username"
|
||||
br()
|
||||
"Must be at least 3 characters"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
label(.class("input validator w-full")) {
|
||||
SVG(.email)
|
||||
|
||||
155
Sources/ViewController/Views/User/UserProfileForm.swift
Normal file
155
Sources/ViewController/Views/User/UserProfileForm.swift
Normal file
@@ -0,0 +1,155 @@
|
||||
import Elementary
|
||||
import ElementaryHTMX
|
||||
import ManualDCore
|
||||
import Styleguide
|
||||
|
||||
struct UserProfileForm: HTML, Sendable {
|
||||
|
||||
static func id(_ profile: User.Profile?) -> String {
|
||||
let base = "userProfileForm"
|
||||
guard let profile else { return base }
|
||||
return "\(base)_\(profile.id.idString)"
|
||||
}
|
||||
|
||||
let userID: User.ID
|
||||
let profile: User.Profile?
|
||||
let dismiss: Bool
|
||||
let signup: Bool
|
||||
|
||||
init(
|
||||
userID: User.ID,
|
||||
profile: User.Profile? = nil,
|
||||
dismiss: Bool,
|
||||
signup: Bool = false
|
||||
) {
|
||||
self.userID = userID
|
||||
self.profile = profile
|
||||
self.dismiss = dismiss
|
||||
self.signup = signup
|
||||
}
|
||||
|
||||
var route: String {
|
||||
guard !signup else {
|
||||
return SiteRoute.View.router.path(for: .signup(.index))
|
||||
.appendingPath("profile")
|
||||
}
|
||||
return SiteRoute.View.router.path(for: .user(.profile(.index)))
|
||||
.appendingPath(profile?.id)
|
||||
}
|
||||
|
||||
var body: some HTML {
|
||||
ModalForm(id: Self.id(profile), closeButton: dismiss, dismiss: dismiss) {
|
||||
|
||||
h1(.class("text-xl font-bold pb-6")) { "Profile" }
|
||||
|
||||
form(
|
||||
.class("grid grid-cols-1 gap-4 p-4"),
|
||||
profile == nil
|
||||
? .hx.post(route)
|
||||
: .hx.patch(route),
|
||||
.hx.target("body"),
|
||||
.hx.swap(.outerHTML)
|
||||
) {
|
||||
if let profile {
|
||||
input(.class("hidden"), .name("id"), .value(profile.id))
|
||||
}
|
||||
input(.class("hidden"), .name("userID"), .value(userID))
|
||||
|
||||
label(.class("input w-full")) {
|
||||
span(.class("label")) { "First Name" }
|
||||
input(.name("firstName"), .required, .autofocus)
|
||||
}
|
||||
|
||||
label(.class("input w-full")) {
|
||||
span(.class("label")) { "Last Name" }
|
||||
input(.name("lastName"), .required)
|
||||
}
|
||||
|
||||
label(.class("input w-full")) {
|
||||
span(.class("label")) { "Company" }
|
||||
input(.name("companyName"), .required)
|
||||
}
|
||||
|
||||
label(.class("input w-full")) {
|
||||
span(.class("label")) { "Address" }
|
||||
input(.name("streetAddress"), .required)
|
||||
}
|
||||
|
||||
label(.class("input w-full")) {
|
||||
span(.class("label")) { "City" }
|
||||
input(.name("city"), .required)
|
||||
}
|
||||
|
||||
label(.class("input w-full")) {
|
||||
span(.class("label")) { "State" }
|
||||
input(.name("state"), .required)
|
||||
}
|
||||
|
||||
label(.class("input w-full")) {
|
||||
span(.class("label")) { "Zip" }
|
||||
input(.name("zipCode"), .required)
|
||||
}
|
||||
|
||||
div(.class("dropdown dropdown-top")) {
|
||||
div(.class("input btn m-1 w-full"), .tabindex(0), .role(.init(rawValue: "button"))) {
|
||||
"Theme"
|
||||
SVG(.chevronDown)
|
||||
}
|
||||
ul(
|
||||
.tabindex(-1),
|
||||
.class("dropdown-content bg-base-300 rounded-box z-1 p-2 shadow-2xl")
|
||||
) {
|
||||
li {
|
||||
input(
|
||||
.type(.radio),
|
||||
.name("theme"),
|
||||
.class("theme-controller w-full btn btn-sm btn-block btn-ghost justify-start"),
|
||||
.init(name: "aria-label", value: "Default"),
|
||||
.value("default")
|
||||
)
|
||||
.attributes(.checked, when: profile?.theme == .default)
|
||||
}
|
||||
li {
|
||||
span(.class("text-sm font-bold text-gray-400")) {
|
||||
"Light"
|
||||
}
|
||||
}
|
||||
for theme in Theme.lightThemes {
|
||||
li {
|
||||
input(
|
||||
.type(.radio),
|
||||
.name("theme"),
|
||||
.class("theme-controller w-full btn btn-sm btn-block btn-ghost justify-start"),
|
||||
.init(name: "aria-label", value: "\(theme.rawValue.capitalized)"),
|
||||
.value(theme.rawValue)
|
||||
)
|
||||
.attributes(.checked, when: profile?.theme == theme)
|
||||
}
|
||||
}
|
||||
li {
|
||||
span(.class("text-sm font-bold text-gray-400")) {
|
||||
"Dark"
|
||||
}
|
||||
}
|
||||
for theme in Theme.darkThemes {
|
||||
li {
|
||||
input(
|
||||
.type(.radio),
|
||||
.name("theme"),
|
||||
.class("theme-controller w-full btn btn-sm btn-block btn-ghost justify-start"),
|
||||
.init(name: "aria-label", value: "\(theme.rawValue.capitalized)"),
|
||||
.value(theme.rawValue)
|
||||
)
|
||||
.attributes(.checked, when: profile?.theme == theme)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SubmitButton()
|
||||
.attributes(.class("btn-block"))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Sources/ViewController/Views/User/UserView.swift
Normal file
53
Sources/ViewController/Views/User/UserView.swift
Normal file
@@ -0,0 +1,53 @@
|
||||
import Elementary
|
||||
import ManualDCore
|
||||
import Styleguide
|
||||
|
||||
struct UserView: HTML, Sendable {
|
||||
let user: User
|
||||
let profile: User.Profile?
|
||||
|
||||
var body: some HTML {
|
||||
div {
|
||||
Row {
|
||||
h1(.class("text-2xl font-bold")) { "Account" }
|
||||
EditButton()
|
||||
.attributes(.showModal(id: UserProfileForm.id(profile)))
|
||||
}
|
||||
|
||||
if let profile {
|
||||
table(.class("table table-zebra")) {
|
||||
tr {
|
||||
td { Label("Name") }
|
||||
td { "\(profile.firstName) \(profile.lastName)" }
|
||||
}
|
||||
tr {
|
||||
td { Label("Company") }
|
||||
td { profile.companyName }
|
||||
}
|
||||
tr {
|
||||
td { Label("Street Address") }
|
||||
td { profile.streetAddress }
|
||||
}
|
||||
tr {
|
||||
td { Label("City") }
|
||||
td { profile.city }
|
||||
}
|
||||
tr {
|
||||
td { Label("State") }
|
||||
td { profile.state }
|
||||
}
|
||||
tr {
|
||||
td { Label("Zip Code") }
|
||||
td { profile.zipCode }
|
||||
}
|
||||
tr {
|
||||
td { Label("Theme") }
|
||||
td { profile.theme?.rawValue ?? "" }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
UserProfileForm(userID: user.id, profile: profile, dismiss: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user