auth.go
⎇
Raw
1package views
2
3import (
4 "strconv"
5
6 "hearthforge/internal/config"
7
8 g "maragu.dev/gomponents"
9 . "maragu.dev/gomponents/html"
10)
11
12// Login renders the sign-in page. error is empty when there is none.
13func Login(cfg *config.Config, errMsg string) g.Node {
14 return Layout(Page{Title: "Sign in", Cfg: cfg},
15 Div(Class("auth-container"),
16 H1(Class("page-title"), g.Text("Sign in")),
17 g.If(errMsg != "", P(Class("form-error"), g.Text(errMsg))),
18 Form(Method("POST"), Action("/login"), Class("auth-form"),
19 Div(Class("form-group"),
20 Label(For("username"), g.Text("Username")),
21 Input(ID("username"), Name("username"), Type("text"), Required(),
22 AutoComplete("username")),
23 ),
24 Div(ID("passkey-section"), Style("display:none"),
25 Button(ID("passkey-btn"), Class("btn btn-secondary btn-block"), Type("button"),
26 g.Text("Sign in with passkey")),
27 P(ID("passkey-error"), Style("display:none")),
28 Div(Class("auth-divider"), Span(g.Text("or"))),
29 ),
30 Div(Class("form-group"),
31 Label(For("password"), g.Text("Password")),
32 Input(ID("password"), Name("password"), Type("password"), Required(),
33 AutoComplete("current-password")),
34 ),
35 Button(Type("submit"), Class("btn btn-primary btn-block"), g.Text("Sign in with password")),
36 ),
37 P(Class("auth-footer"),
38 g.Text("Don't have an account? "), A(Href("/register"), g.Text("Register"))),
39 ),
40 Script(Type("module"), Src("/assets/passkey-login.js")),
41 )
42}
43
44// Register renders the registration page. pending shows the queue notice
45// instead of the form. question is the optional application question.
46func Register(cfg *config.Config, errMsg, question string, pending bool) g.Node {
47 if pending {
48 return Layout(Page{Title: "Register", Cfg: cfg},
49 Div(Class("auth-container"),
50 H1(Class("page-title"), g.Text("Create account")),
51 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.")),
52 P(Class("auth-footer"),
53 g.Text("Already have an account? "), A(Href("/login"), g.Text("Sign in"))),
54 ),
55 )
56 }
57 return Layout(Page{Title: "Register", Cfg: cfg},
58 Div(Class("auth-container"),
59 H1(Class("page-title"), g.Text("Create account")),
60 g.If(errMsg != "", P(Class("form-error"), g.Text(errMsg))),
61 Form(Method("POST"), Action("/register"), Class("auth-form"),
62 Div(Class("form-group"),
63 Label(For("username"), g.Text("Username")),
64 Input(ID("username"), Name("username"), Type("text"), Required(),
65 AutoComplete("username"), MaxLength(strconv.Itoa(cfg.MaxUsernameBytes)),
66 Pattern("[a-zA-Z0-9_-]+"),
67 Title("Letters, numbers, hyphens and underscores only")),
68 ),
69 g.If(question != "", Div(Class("form-group"),
70 Label(For("application"), g.Text(question)),
71 Textarea(ID("application"), Name("application"), Rows("4"), Required(),
72 Placeholder("Your answer...")),
73 )),
74 Div(ID("passkey-section"), Style("display:none"),
75 Button(ID("passkey-register-btn"), Class("btn btn-secondary btn-block"),
76 Type("button"), g.Text("Register with passkey")),
77 P(ID("passkey-error"), Style("display:none")),
78 Div(Class("auth-divider"), Span(g.Text("or"))),
79 ),
80 Div(Class("form-group"),
81 Label(For("password"), g.Text("Password")),
82 Input(ID("password"), Name("password"), Type("password"),
83 AutoComplete("new-password"), MinLength("8"),
84 MaxLength(strconv.Itoa(cfg.MaxPasswordBytes))),
85 ),
86 Div(Class("form-group"),
87 Label(For("password2"), g.Text("Confirm password")),
88 Input(ID("password2"), Name("password2"), Type("password"),
89 AutoComplete("new-password"), MinLength("8"),
90 MaxLength(strconv.Itoa(cfg.MaxPasswordBytes))),
91 ),
92 Button(Type("submit"), Class("btn btn-primary btn-block"), g.Text("Register with password")),
93 ),
94 P(Class("auth-footer"),
95 g.Text("Already have an account? "), A(Href("/login"), g.Text("Sign in"))),
96 ),
97 Script(Type("module"), Src("/assets/passkey-register.js")),
98 )
99}
100