User creation validation

This commit is contained in:
Adriano Caloiaro 2023-07-29 08:06:30 -06:00
parent afc096d1d1
commit e768c99db0
No known key found for this signature in database
GPG key ID: C2BC56DE73CE3F75
9 changed files with 198 additions and 47 deletions

View file

@ -6,21 +6,50 @@ title: Create Account
<div class="auth-wrapper">
<div class="auth-form">
<form hx-post="{{ .Site.Params.apiBaseUrl }}/users/create">
<label for="username">Username</label>
<input id="username" type="text" name="username" placeholder="desired username"/>
<div hx-target="this" hx-swap="innerHTML">
<label for="username">Username</label>
<input id="username"
type="text"
name="username"
placeholder="desired username"
hx-post="{{ .Site.Params.apiBaseUrl }}/users/new_user_validation"
hx-indicator="#username-ind">
<img id="username-ind" src="/img/bars.svg" class="htmx-indicator"/>
<label for="email">Email (we do not spam or sell information)</label>
<input id="email" type="email" name="email" placeholder="thot_ldr@email.com">
<label for="email">Email (we do not spam or sell information)</label>
<input
id="email"
type="email"
name="email"
placeholder="thot_ldr@email.com"
hx-post="{{ .Site.Params.apiBaseUrl }}/users/new_user_validation"
hx-indicator="#email-ind">
<img id="email-ind" src="/img/bars.svg" class="htmx-indicator"/>
<label for="password">Password</label>
<input id="password type="password" name="password" placeholder="desired password">
<label for="password">Password</label>
<input
id="password
type="password"
name="password"
placeholder="desired password"
hx-post="{{ .Site.Params.apiBaseUrl }}/users/new_user_validation"
hx-indicator="#password-ind">
<img id="password-ind" src="/img/bars.svg" class="htmx-indicator"/>
<label for="password_confirmation">Password Confirmation</label>
<input id="password_confirmation" type="text" name="password_confirmation" placeholder="desired password again">
<br>
<br>
<button class="btn btn-success">Create Account</button>
<label for="password_confirmation">Password Confirmation</label>
<input
id="password_confirmation"
type="text"
name="password_confirmation"
placeholder="desired password again"
hx-post="{{ .Site.Params.apiBaseUrl }}/users/new_user_validation"
hx-indicator="#password-conf-ind">
<img id="password-conf-ind" src="/img/bars.svg" class="htmx-indicator"/>
<br>
<br>
<button disabled>Create Account</button>
</div>
</form>
</div>
</div>

View file

@ -5,17 +5,17 @@ title: Log in
<div class="auth-wrapper">
<div class="auth-form">
<form hx-post="{{ .Site.Params.apiBaseUrl }}/users/login">
<div hx-target="this" hx-swap="outerHTML">
<div hx-target="this" hx-swap="innerHTML">
<label for="username">Username</label>
<input id="username" type="text" name="username" placeholder="desired username" hx-post="{{ .Site.Params.apiBaseUrl }}/users/login">
<img id="ind" src="/img/bars.svg" class="htmx-indicator"/>
<label for="password">Password</label>
<input id="password" type="password" name="password" placeholder="desired password">
<br>
<br>
<button class="btn btn-success">Log In</button>
</div>
<label for="password">Password</label>
<input id="password" type="password" name="password" placeholder="desired password">
<br>
<br>
<button class="btn btn-success">Log In</button>
</form>
</div>
</div>

8
go.mod
View file

@ -2,13 +2,13 @@ module github.com/acaloiaro/garbage_speak
go 1.20
replace github.com/acaloiaro/neoq v0.17.0 => /home/adriano/git/neoq
require (
github.com/acaloiaro/neoq v0.17.0
github.com/acaloiaro/neoq v0.18.0
github.com/alexedwards/scs/pgxstore v0.0.0-20230327161757-10d4299e3b24
github.com/alexedwards/scs/v2 v2.5.1
github.com/georgysavva/scany/v2 v2.0.0
github.com/go-chi/chi/v5 v5.0.10
github.com/go-chi/cors v1.2.1
github.com/gofrs/uuid v4.4.0+incompatible
github.com/golang-migrate/migrate/v4 v4.16.2
github.com/jackc/pgx-gofrs-uuid v0.0.0-20230224015001-1d428863c2e2
@ -17,8 +17,6 @@ require (
)
require (
github.com/go-chi/chi/v5 v5.0.10 // indirect
github.com/go-chi/cors v1.2.1 // indirect
github.com/gofrs/uuid/v5 v5.0.0 // indirect
github.com/guregu/null v4.0.0+incompatible // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect

2
go.sum
View file

@ -1,6 +1,8 @@
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/acaloiaro/neoq v0.18.0 h1:WKTDNEL/sH5hlwd/7Kgbqb4kKdvNVdzNNjfKRtt3v5k=
github.com/acaloiaro/neoq v0.18.0/go.mod h1:uLI2JfC2V9p+H5lx5s0SUsjxIYLm8JDhi6VkyURMvLM=
github.com/alexedwards/scs/pgxstore v0.0.0-20230327161757-10d4299e3b24 h1:l8+ZTNz/s7fbpTz/oeUmXCL39Rn49SRaXnj2/LZzCos=
github.com/alexedwards/scs/pgxstore v0.0.0-20230327161757-10d4299e3b24/go.mod h1:KTB1slYgIU/BBa4MWA5IdET31OsbPQBM7dqDw5klIt4=
github.com/alexedwards/scs/v2 v2.5.1 h1:EhAz3Kb3OSQzD8T+Ub23fKsiuvE0GzbF5Lgn0uTwM3Y=

View file

@ -1,24 +1,5 @@
<style>
.auth-wrapper{
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.auth-wrapper .sign-form{
max-width: 350px;
width: 100%;
padding: 0 20px;
}
.auth-wrapper h1{margin-bottom: 20px;}
</style>
<div class="auth-wrapper">
<div class="sign-form">
<h1>Sign In</h1>
<form action="/users/login" method="POST" hx-target=".auth-wrapper">
<input type="text" name="username">
<input type="text" name="password">

View file

@ -0,0 +1,60 @@
<label for="username">Username</label>
<input
id="username"
type="text"
name="username"
placeholder="desired username"
value="{{ .Username }}"
hx-post="{{ .ApiBaseUrl }}/users/new_user_validation"
hx-indicator="#username-ind">
<img id="username-ind" src="/img/bars.svg" class="htmx-indicator"/>
{{ with .UsernameError }}
<div class="error-message">{{ . }}</div>
{{ end }}
<label for="email">Email (we do not spam or sell information)</label>
<input
id="email"
type="email"
name="email"
placeholder="your email address"
value="{{ .Email }}"
hx-post="{{ .ApiBaseUrl }}/users/new_user_validation"
hx-indicator="#email-ind">
<img id="email-ind" src="/img/bars.svg" class="htmx-indicator"/>
{{ with .EmailError }}
<div class="error-message">{{ . }}</div>
{{ end }}
<label for="password">Password</label>
<input
id="password"
type="password"
name="password"
placeholder="desired password"
value="{{ .Password }}"
hx-post="{{ .ApiBaseUrl }}/users/new_user_validation"
hx-indicator="#password-ind">
<img id="password-ind" src="/img/bars.svg" class="htmx-indicator"/>
{{ with .PasswordError }}
<div class="error-message">{{ . }}</div>
{{ end }}
<label for="password_confirmation">Password Confirmation</label>
<input
id="password_confirmation"
type="password"
name="password_confirmation"
placeholder="desired password"
value="{{ .PasswordConfirmation }}"
hx-post="{{ .ApiBaseUrl }}/users/new_user_validation"
hx-indicator="#password-conf-ind">
<img id="password-conf-ind" src="/img/bars.svg" class="htmx-indicator"/>
{{ with .PasswordConfirmationError }}
<div class="error-message">{{ . }}</div>
{{ end }}
<br>
<br>
<button {{ if gt .ErrorCount 0 }}disabled=true{{end}}>Create Account</button>

View file

@ -162,6 +162,7 @@ func main() {
r.Get("/garbage_bin", garbageBin)
r.Get("/nav/user_items", navUserItems)
r.Route("/users", func(r chi.Router) {
r.Post("/new_user_validation", newUserValidationHandler)
r.Get("/create", newAccountHandler)
r.Post("/login", loginHandler)
r.Post("/create", createAccount)
@ -175,8 +176,72 @@ func main() {
}
}
// loginHandler renders the login page
// newUserValidationHandler checks whether a username is available during account creation
func newUserValidationHandler(w http.ResponseWriter, r *http.Request) {
tmplVars := map[string]any{"ApiBaseUrl": apiURL()}
tmpl := template.Must(template.ParseFiles("partials/users/new_user_validation.html"))
errCnt := 0
if err := r.ParseForm(); err != nil {
ise(err, w)
return
}
username := r.PostForm.Get("username")
email := r.PostForm.Get("email")
password := r.PostForm.Get("password")
passwordConfirmation := r.PostForm.Get("password_confirmation")
tmplVars["Username"] = username
tmplVars["Email"] = email
tmplVars["Password"] = password
tmplVars["PasswordConfirmation"] = passwordConfirmation
log.Println("vars", tmplVars)
if len(username) == 0 {
tmplVars["UsernameError"] = "Please choose a username"
errCnt += 1
} else if len(username) > 0 {
var userID string
db.QueryRow(r.Context(), "SELECT id FROM users WHERE username = $1", username).Scan(&userID)
if userID != "" {
tmplVars["UsernameError"] = fmt.Sprintf("Username '%s' is unavailable. Choose a different username.", username)
errCnt += 1
}
}
if (len(email) > 0 && len(email) < 4) || (len(email) >= 4 && !strings.Contains(email, "@")) {
tmplVars["EmailError"] = "Please enter a valid email address."
errCnt += 1
}
if len(password) > 0 && len(password) < 8 {
tmplVars["PasswordError"] = "Please choose a password greater than 8 characters"
errCnt += 1
}
if len(password) > 0 && len(passwordConfirmation) > 0 && password != passwordConfirmation {
tmplVars["PasswordConfirmationError"] = "Passwords do not match"
errCnt += 1
}
tmplVars["ErrorCount"] = errCnt
var buff = bytes.NewBufferString("")
err := tmpl.Execute(buff, tmplVars)
if err != nil {
ise(err, w)
return
}
log.Println("respones", buff.String())
w.Header().Add("Content-Type", "text/html")
w.WriteHeader(http.StatusOK)
w.Write(buff.Bytes())
}
// loginHandler renders the login page
func loginHandler(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
ise(err, w)

View file

@ -42,7 +42,7 @@ input {
}
}
.auth-wrapper{
.auth-wrapper {
height: 100%;
display: flex;
align-items: center;
@ -50,9 +50,7 @@ input {
}
.auth-wrapper .auth-form {
max-width: 450px;
width: 100%;
padding: 0 20px;
}
.auth-wrapper h1{margin-bottom: 20px;}
@ -62,3 +60,17 @@ label {
margin: 0.5rem 0;
}
.error-message {
color: red;
}
.htmx-indicator {
display: inline-block;
}
button:disabled,
button[disabled]{
border: 1px solid #999999;
background-color: #cccccc;
color: #666666;
}

View file

@ -0,0 +1,4 @@
{{ $file := .Get 0 }}
{{ $file | readFile | safeHTML }}