feat: adds next route to login.
This commit is contained in:
@@ -6,7 +6,9 @@ import Vapor
|
|||||||
private let viewRouteMiddleware: [any Middleware] = [
|
private let viewRouteMiddleware: [any Middleware] = [
|
||||||
UserPasswordAuthenticator(),
|
UserPasswordAuthenticator(),
|
||||||
UserSessionAuthenticator(),
|
UserSessionAuthenticator(),
|
||||||
User.redirectMiddleware(path: "/login"),
|
User.redirectMiddleware { req in
|
||||||
|
"/login?next=\(req.url.string)"
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
extension SiteRoute.View {
|
extension SiteRoute.View {
|
||||||
@@ -14,8 +16,7 @@ extension SiteRoute.View {
|
|||||||
switch self {
|
switch self {
|
||||||
case .project,
|
case .project,
|
||||||
.frictionRate,
|
.frictionRate,
|
||||||
.effectiveLength,
|
.effectiveLength:
|
||||||
.room:
|
|
||||||
return viewRouteMiddleware
|
return viewRouteMiddleware
|
||||||
case .login, .signup:
|
case .login, .signup:
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ extension SiteRoute {
|
|||||||
case login(LoginRoute)
|
case login(LoginRoute)
|
||||||
case signup(SignupRoute)
|
case signup(SignupRoute)
|
||||||
case project(ProjectRoute)
|
case project(ProjectRoute)
|
||||||
case room(RoomRoute)
|
|
||||||
case frictionRate(FrictionRateRoute)
|
case frictionRate(FrictionRateRoute)
|
||||||
case effectiveLength(EffectiveLengthRoute)
|
case effectiveLength(EffectiveLengthRoute)
|
||||||
// case user(UserRoute)
|
// case user(UserRoute)
|
||||||
@@ -25,9 +24,6 @@ extension SiteRoute {
|
|||||||
Route(.case(Self.project)) {
|
Route(.case(Self.project)) {
|
||||||
SiteRoute.View.ProjectRoute.router
|
SiteRoute.View.ProjectRoute.router
|
||||||
}
|
}
|
||||||
Route(.case(Self.room)) {
|
|
||||||
SiteRoute.View.RoomRoute.router
|
|
||||||
}
|
|
||||||
Route(.case(Self.frictionRate)) {
|
Route(.case(Self.frictionRate)) {
|
||||||
SiteRoute.View.FrictionRateRoute.router
|
SiteRoute.View.FrictionRateRoute.router
|
||||||
}
|
}
|
||||||
@@ -44,7 +40,7 @@ extension SiteRoute {
|
|||||||
extension SiteRoute.View {
|
extension SiteRoute.View {
|
||||||
public enum ProjectRoute: Equatable, Sendable {
|
public enum ProjectRoute: Equatable, Sendable {
|
||||||
case create(Project.Create)
|
case create(Project.Create)
|
||||||
case detail(Project.ID)
|
case detail(Project.ID, DetailRoute)
|
||||||
case form(dismiss: Bool = false)
|
case form(dismiss: Bool = false)
|
||||||
case index
|
case index
|
||||||
case page(page: Int = 1, limit: Int = 25)
|
case page(page: Int = 1, limit: Int = 25)
|
||||||
@@ -71,7 +67,7 @@ extension SiteRoute.View {
|
|||||||
rootPath
|
rootPath
|
||||||
Project.ID.parser()
|
Project.ID.parser()
|
||||||
}
|
}
|
||||||
Method.get
|
DetailRoute.router
|
||||||
}
|
}
|
||||||
Route(.case(Self.form)) {
|
Route(.case(Self.form)) {
|
||||||
Path {
|
Path {
|
||||||
@@ -102,24 +98,33 @@ extension SiteRoute.View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SiteRoute.View {
|
extension SiteRoute.View.ProjectRoute {
|
||||||
public enum RoomRoute: Equatable, Sendable {
|
|
||||||
case form(Project.ID, dismiss: Bool = false)
|
|
||||||
case index(Project.ID)
|
|
||||||
case submit(Project.ID, Room.Form)
|
|
||||||
|
|
||||||
static let rootPath = Path {
|
public enum DetailRoute: Equatable, Sendable {
|
||||||
ProjectRoute.rootPath
|
case index
|
||||||
Project.ID.parser()
|
case rooms(RoomRoute)
|
||||||
"rooms"
|
|
||||||
|
static let router = OneOf {
|
||||||
|
Route(.case(Self.index)) {
|
||||||
|
Method.get
|
||||||
|
}
|
||||||
|
Route(.case(Self.rooms)) {
|
||||||
|
RoomRoute.router
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RoomRoute: Equatable, Sendable {
|
||||||
|
case form(dismiss: Bool = false)
|
||||||
|
case index
|
||||||
|
case submit(Room.Form)
|
||||||
|
|
||||||
|
static let rootPath = "rooms"
|
||||||
|
|
||||||
public static let router = OneOf {
|
public static let router = OneOf {
|
||||||
Route(.case(Self.form)) {
|
Route(.case(Self.form)) {
|
||||||
Path {
|
Path {
|
||||||
ProjectRoute.rootPath
|
rootPath
|
||||||
Project.ID.parser()
|
|
||||||
"rooms"
|
|
||||||
"create"
|
"create"
|
||||||
}
|
}
|
||||||
Method.get
|
Method.get
|
||||||
@@ -128,11 +133,13 @@ extension SiteRoute.View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Route(.case(Self.index)) {
|
Route(.case(Self.index)) {
|
||||||
rootPath
|
Path {
|
||||||
|
rootPath
|
||||||
|
}
|
||||||
Method.get
|
Method.get
|
||||||
}
|
}
|
||||||
Route(.case(Self.submit)) {
|
Route(.case(Self.submit)) {
|
||||||
rootPath
|
Path { rootPath }
|
||||||
Method.post
|
Method.post
|
||||||
Body {
|
Body {
|
||||||
FormData {
|
FormData {
|
||||||
@@ -241,7 +248,7 @@ extension SiteRoute.View.EffectiveLengthRoute {
|
|||||||
extension SiteRoute.View {
|
extension SiteRoute.View {
|
||||||
|
|
||||||
public enum LoginRoute: Equatable, Sendable {
|
public enum LoginRoute: Equatable, Sendable {
|
||||||
case index
|
case index(next: String? = nil)
|
||||||
case submit(User.Login)
|
case submit(User.Login)
|
||||||
|
|
||||||
static let rootPath = "login"
|
static let rootPath = "login"
|
||||||
@@ -250,6 +257,13 @@ extension SiteRoute.View {
|
|||||||
Route(.case(Self.index)) {
|
Route(.case(Self.index)) {
|
||||||
Path { rootPath }
|
Path { rootPath }
|
||||||
Method.get
|
Method.get
|
||||||
|
Query {
|
||||||
|
Optionally {
|
||||||
|
Field("next", default: nil) {
|
||||||
|
CharacterSet.urlPathAllowed.map(.string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Route(.case(Self.submit)) {
|
Route(.case(Self.submit)) {
|
||||||
Path { rootPath }
|
Path { rootPath }
|
||||||
@@ -258,6 +272,11 @@ extension SiteRoute.View {
|
|||||||
FormData {
|
FormData {
|
||||||
Field("email", .string)
|
Field("email", .string)
|
||||||
Field("password", .string)
|
Field("password", .string)
|
||||||
|
Optionally {
|
||||||
|
Field("next", default: nil) {
|
||||||
|
CharacterSet.urlPathAllowed.map(.string)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.map(.memberwise(User.Login.init))
|
.map(.memberwise(User.Login.init))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,10 +48,12 @@ extension User {
|
|||||||
public struct Login: Codable, Equatable, Sendable {
|
public struct Login: Codable, Equatable, Sendable {
|
||||||
public let email: String
|
public let email: String
|
||||||
public let password: String
|
public let password: String
|
||||||
|
public let next: String?
|
||||||
|
|
||||||
public init(email: String, password: String) {
|
public init(email: String, password: String, next: String? = nil) {
|
||||||
self.email = email
|
self.email = email
|
||||||
self.password = password
|
self.password = password
|
||||||
|
self.next = next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,15 +13,14 @@ extension ViewController.Request {
|
|||||||
switch route {
|
switch route {
|
||||||
case .login(let route):
|
case .login(let route):
|
||||||
switch route {
|
switch route {
|
||||||
case .index:
|
case .index(let next):
|
||||||
return view {
|
return view {
|
||||||
LoginForm()
|
LoginForm(next: next)
|
||||||
}
|
}
|
||||||
case .submit(let login):
|
case .submit(let login):
|
||||||
let user = try await authenticate(login)
|
let _ = try await authenticate(login)
|
||||||
let projects = try await database.projects.fetch(user.id, .init(page: 1, per: 25))
|
|
||||||
return view {
|
return view {
|
||||||
ProjectsTable(userID: user.id, projects: projects)
|
LoggedIn(next: login.next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .signup(let route):
|
case .signup(let route):
|
||||||
@@ -40,8 +39,8 @@ extension ViewController.Request {
|
|||||||
}
|
}
|
||||||
case .project(let route):
|
case .project(let route):
|
||||||
return try await route.renderView(on: self)
|
return try await route.renderView(on: self)
|
||||||
case .room(let route):
|
// case .room(let route):
|
||||||
return try await route.renderView(on: self)
|
// return try await route.renderView(on: self)
|
||||||
case .frictionRate(let route):
|
case .frictionRate(let route):
|
||||||
return try await route.renderView(isHtmxRequest: isHtmxRequest)
|
return try await route.renderView(isHtmxRequest: isHtmxRequest)
|
||||||
case .effectiveLength(let route):
|
case .effectiveLength(let route):
|
||||||
@@ -102,29 +101,40 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case .detail(let projectID):
|
case .detail(let projectID, let route):
|
||||||
let project = try await database.projects.get(projectID)!
|
switch route {
|
||||||
return request.view {
|
case .index:
|
||||||
ProjectView(projectID: projectID, activeTab: .projects) {
|
let project = try await database.projects.get(projectID)!
|
||||||
ProjectDetail(project: project)
|
return request.view {
|
||||||
|
ProjectView(projectID: projectID, activeTab: .projects) {
|
||||||
|
ProjectDetail(project: project)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
case .rooms(let route):
|
||||||
|
return try await route.renderView(on: request, projectID: projectID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// case .rooms(let projectID, let route):
|
||||||
|
// return try await route.renderView(on: request, projectID: projectID)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SiteRoute.View.RoomRoute {
|
extension SiteRoute.View.ProjectRoute.RoomRoute {
|
||||||
func renderView(on request: ViewController.Request) async throws -> AnySendableHTML {
|
func renderView(
|
||||||
|
on request: ViewController.Request,
|
||||||
|
projectID: Project.ID
|
||||||
|
) async throws -> AnySendableHTML {
|
||||||
@Dependency(\.database) var database
|
@Dependency(\.database) var database
|
||||||
|
|
||||||
switch self {
|
switch self {
|
||||||
|
|
||||||
case .form(let projectID, let dismiss):
|
case .form(let dismiss):
|
||||||
return RoomForm(dismiss: dismiss, projectID: projectID)
|
return RoomForm(dismiss: dismiss, projectID: projectID)
|
||||||
|
|
||||||
case .index(let projectID):
|
case .index:
|
||||||
let rooms = try await database.rooms.fetch(projectID)
|
let rooms = try await database.rooms.fetch(projectID)
|
||||||
return request.view {
|
return request.view {
|
||||||
ProjectView(projectID: projectID, activeTab: .rooms) {
|
ProjectView(projectID: projectID, activeTab: .rooms) {
|
||||||
@@ -132,7 +142,7 @@ extension SiteRoute.View.RoomRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case .submit(let projectID, let form):
|
case .submit(let form):
|
||||||
request.logger.debug("New room form submitted.")
|
request.logger.debug("New room form submitted.")
|
||||||
let _ = try await database.rooms.create(.init(form: form, projectID: projectID))
|
let _ = try await database.rooms.create(.init(form: form, projectID: projectID))
|
||||||
let rooms = try await database.rooms.fetch(projectID)
|
let rooms = try await database.rooms.fetch(projectID)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import Elementary
|
import Elementary
|
||||||
|
import ElementaryHTMX
|
||||||
|
import ManualDCore
|
||||||
|
import Styleguide
|
||||||
|
|
||||||
public struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable {
|
public struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable {
|
||||||
|
|
||||||
@@ -6,16 +9,10 @@ public struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable
|
|||||||
public var lang: String { "en" }
|
public var lang: String { "en" }
|
||||||
|
|
||||||
let inner: Inner
|
let inner: Inner
|
||||||
// let activeTab: Sidebar.ActiveTab
|
|
||||||
// let showSidebar: Bool
|
|
||||||
|
|
||||||
init(
|
init(
|
||||||
// active activeTab: Sidebar.ActiveTab,
|
|
||||||
// showSidebar: Bool = true,
|
|
||||||
_ inner: () -> Inner
|
_ inner: () -> Inner
|
||||||
) {
|
) {
|
||||||
// self.activeTab = activeTab
|
|
||||||
// self.showSidebar = showSidebar
|
|
||||||
self.inner = inner()
|
self.inner = inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,4 +36,21 @@ public struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct LoggedIn: HTML, Sendable {
|
||||||
|
let next: String?
|
||||||
|
|
||||||
|
var body: some HTML {
|
||||||
|
div(
|
||||||
|
.hx.get(next ?? SiteRoute.View.router.path(for: .project(.index))),
|
||||||
|
.hx.pushURL(true),
|
||||||
|
.hx.target("body"),
|
||||||
|
.hx.trigger(.event(.revealed)),
|
||||||
|
.hx.indicator(".hx-indicator")
|
||||||
|
) {
|
||||||
|
Indicator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public protocol SendableHTMLDocument: HTMLDocument, Sendable {}
|
public protocol SendableHTMLDocument: HTMLDocument, Sendable {}
|
||||||
|
|||||||
@@ -56,10 +56,10 @@ struct Sidebar: HTML {
|
|||||||
}
|
}
|
||||||
.attributes(.class("p-4"))
|
.attributes(.class("p-4"))
|
||||||
|
|
||||||
row(title: "Project", icon: .mapPin, route: .project(.detail(projectID)))
|
row(title: "Project", icon: .mapPin, route: .project(.detail(projectID, .index)))
|
||||||
.attributes(.data("active", value: active == .projects ? "true" : "false"))
|
.attributes(.data("active", value: active == .projects ? "true" : "false"))
|
||||||
|
|
||||||
row(title: "Rooms", icon: .doorClosed, route: .room(.index(projectID)))
|
row(title: "Rooms", icon: .doorClosed, route: .project(.detail(projectID, .rooms(.index))))
|
||||||
.attributes(.data("active", value: active == .rooms ? "true" : "false"))
|
.attributes(.data("active", value: active == .rooms ? "true" : "false"))
|
||||||
|
|
||||||
row(title: "Equivalent Lengths", icon: .rulerDimensionLine, route: .effectiveLength(.index))
|
row(title: "Equivalent Lengths", icon: .rulerDimensionLine, route: .effectiveLength(.index))
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ extension ProjectsTable {
|
|||||||
td {
|
td {
|
||||||
a(
|
a(
|
||||||
.class("btn btn-success"),
|
.class("btn btn-success"),
|
||||||
.href(route: .project(.detail(project.id)))
|
.href(route: .project(.detail(project.id, .index)))
|
||||||
) { ">" }
|
) { ">" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ struct RoomForm: HTML, Sendable {
|
|||||||
// TODO: Use htmx here.
|
// TODO: Use htmx here.
|
||||||
form(
|
form(
|
||||||
.method(.post),
|
.method(.post),
|
||||||
.action(route: .room(.index(projectID)))
|
.action(route: .project(.detail(projectID, .rooms(.index))))
|
||||||
) {
|
) {
|
||||||
div {
|
div {
|
||||||
label(.for("name")) { "Name:" }
|
label(.for("name")) { "Name:" }
|
||||||
@@ -45,7 +45,7 @@ struct RoomForm: HTML, Sendable {
|
|||||||
div(.class("space-x-4")) {
|
div(.class("space-x-4")) {
|
||||||
CancelButton()
|
CancelButton()
|
||||||
.attributes(
|
.attributes(
|
||||||
.hx.get(route: .room(.form(projectID, dismiss: true))),
|
.hx.get(route: .project(.detail(projectID, .rooms(.form(dismiss: true))))),
|
||||||
.hx.target("#roomForm"),
|
.hx.target("#roomForm"),
|
||||||
.hx.swap(.outerHTML)
|
.hx.swap(.outerHTML)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ struct RoomsView: HTML, Sendable {
|
|||||||
.data("tip", value: "Add room")
|
.data("tip", value: "Add room")
|
||||||
) {
|
) {
|
||||||
button(
|
button(
|
||||||
.hx.get(route: .room(.form(projectID, dismiss: false))),
|
.hx.get(route: .project(.detail(projectID, .rooms(.form(dismiss: false))))),
|
||||||
.hx.target("#roomForm"),
|
.hx.target("#roomForm"),
|
||||||
.hx.swap(.outerHTML),
|
.hx.swap(.outerHTML),
|
||||||
.class("btn btn-primary w-[40px] text-2xl")
|
.class("btn btn-primary w-[40px] text-2xl")
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import Styleguide
|
|||||||
struct LoginForm: HTML, Sendable {
|
struct LoginForm: HTML, Sendable {
|
||||||
|
|
||||||
let style: Style
|
let style: Style
|
||||||
|
let next: String?
|
||||||
|
|
||||||
init(style: Style = .login) {
|
init(style: Style = .login, next: String? = nil) {
|
||||||
self.style = style
|
self.style = style
|
||||||
|
self.next = next
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
@@ -18,6 +20,11 @@ struct LoginForm: HTML, Sendable {
|
|||||||
form(
|
form(
|
||||||
.method(.post)
|
.method(.post)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
if let next {
|
||||||
|
input(.class("hidden"), .name("next"), .value(next))
|
||||||
|
}
|
||||||
|
|
||||||
fieldset(.class("fieldset bg-base-200 border-base-300 rounded-box w-xl border p-4")) {
|
fieldset(.class("fieldset bg-base-200 border-base-300 rounded-box w-xl border p-4")) {
|
||||||
legend(.class("fieldset-legend")) { style.title }
|
legend(.class("fieldset-legend")) { style.title }
|
||||||
|
|
||||||
@@ -81,7 +88,7 @@ struct LoginForm: HTML, Sendable {
|
|||||||
button(.class("btn btn-secondary mt-4")) { style.title }
|
button(.class("btn btn-secondary mt-4")) { style.title }
|
||||||
a(
|
a(
|
||||||
.class("btn btn-link mt-4"),
|
.class("btn btn-link mt-4"),
|
||||||
.href(route: style == .signup ? .login(.index) : .signup(.index))
|
.href(route: style == .signup ? .login(.index(next: next)) : .signup(.index))
|
||||||
) {
|
) {
|
||||||
style == .login ? "Sign Up" : "Login"
|
style == .login ? "Sign Up" : "Login"
|
||||||
}
|
}
|
||||||
|
|||||||
2217
output.css
2217
output.css
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user