package views import ( "strconv" "hearthforge/internal/config" g "maragu.dev/gomponents" . "maragu.dev/gomponents/html" ) // Login renders the sign-in page. errMsg and next may be empty. next is a // local path to return to after sign-in. func Login(cfg *config.Config, errMsg, next string) g.Node { return Layout(Page{Title: "Sign in", Cfg: cfg}, Div(Class("auth-container"), H1(Class("page-title"), g.Text("Sign in")), g.If(errMsg != "", P(Class("form-error"), g.Text(errMsg))), g.If(errMsg == "" && next != "", P(Class("form-success"), g.Text("Please sign in again to continue."))), Form(Method("POST"), Action("/login"), Class("auth-form"), g.If(next != "", Input(Type("hidden"), Name("next"), Value(next))), Div(Class("form-group"), Label(For("username"), g.Text("Username")), Input(ID("username"), Name("username"), Type("text"), Required(), AutoComplete("username")), ), Div(ID("passkey-section"), Style("display:none"), Button(ID("passkey-btn"), Class("btn btn-secondary btn-block"), Type("button"), g.Text("Sign in with passkey")), P(ID("passkey-error"), Style("display:none")), Div(Class("auth-divider"), Span(g.Text("or"))), ), Div(Class("form-group"), Label(For("password"), g.Text("Password")), Input(ID("password"), Name("password"), Type("password"), Required(), AutoComplete("current-password")), ), Button(Type("submit"), Class("btn btn-primary btn-block"), g.Text("Sign in with password")), ), P(Class("auth-footer"), g.Text("Don't have an account? "), A(Href("/register"), g.Text("Register"))), ), Script(Type("module"), Src("/assets/passkey-login.js")), ) } // Register renders the registration page. pending shows the queue notice // instead of the form. question is the optional application question. func Register(cfg *config.Config, errMsg, question string, pending bool) g.Node { if pending { return Layout(Page{Title: "Register", Cfg: cfg}, Div(Class("auth-container"), H1(Class("page-title"), g.Text("Create account")), P(Class("form-success"), g.Text("Your account has been submitted for review. You will be able to log in once an admin approves it.")), P(Class("auth-footer"), g.Text("Already have an account? "), A(Href("/login"), g.Text("Sign in"))), ), ) } return Layout(Page{Title: "Register", Cfg: cfg}, Div(Class("auth-container"), H1(Class("page-title"), g.Text("Create account")), g.If(errMsg != "", P(Class("form-error"), g.Text(errMsg))), Form(Method("POST"), Action("/register"), Class("auth-form"), Div(Class("form-group"), Label(For("username"), g.Text("Username")), Input(ID("username"), Name("username"), Type("text"), Required(), AutoComplete("username"), MaxLength(strconv.Itoa(cfg.MaxUsernameBytes)), Pattern("[a-zA-Z0-9_-]+"), Title("Letters, numbers, hyphens and underscores only")), ), g.If(question != "", Div(Class("form-group"), Label(For("application"), g.Text(question)), Textarea(ID("application"), Name("application"), Rows("4"), Required(), Placeholder("Your answer...")), )), Div(ID("passkey-section"), Style("display:none"), Button(ID("passkey-register-btn"), Class("btn btn-secondary btn-block"), Type("button"), g.Text("Register with passkey")), P(ID("passkey-error"), Style("display:none")), Div(Class("auth-divider"), Span(g.Text("or"))), ), Div(Class("form-group"), Label(For("password"), g.Text("Password")), Input(ID("password"), Name("password"), Type("password"), AutoComplete("new-password"), MinLength("8"), MaxLength(strconv.Itoa(cfg.MaxPasswordBytes))), ), Div(Class("form-group"), Label(For("password2"), g.Text("Confirm password")), Input(ID("password2"), Name("password2"), Type("password"), AutoComplete("new-password"), MinLength("8"), MaxLength(strconv.Itoa(cfg.MaxPasswordBytes))), ), Button(Type("submit"), Class("btn btn-primary btn-block"), g.Text("Register with password")), ), P(Class("auth-footer"), g.Text("Already have an account? "), A(Href("/login"), g.Text("Sign in"))), ), Script(Type("module"), Src("/assets/passkey-register.js")), ) }