welcome: username error handling (#75)

* username error handling welcome ui

* added short username check with separate error handling function

* nothing happaned here

* added username error handling, form saves info

* pull75 fixes

* pull75 fixes ui

* CSS change

* separate css for errors, added logic for several type of errors

* rename extractErrorMessage

* validation changes

* added validations in api

* changed rendering template, recives errors in JSON format

* rolled back schema and makefile in kratos

* changes in HTML

* combined kratos and manual validations

* fixed rendering and handling JSON error response

* rollback unused index.html

* minor fixes

* refactored the repeated logic of Errors into a separate function

* rollback

* refactor: group errors and form data together

* rollback picocss version

* use picocss 2.0.6

---------

Co-authored-by: Giorgi Lekveishvili <lekva@gl-mbp-m1-max.local>
diff --git a/core/installer/welcome/welcome.go b/core/installer/welcome/welcome.go
index 60adf6b..4d30784 100644
--- a/core/installer/welcome/welcome.go
+++ b/core/installer/welcome/welcome.go
@@ -5,6 +5,7 @@
 	"embed"
 	"encoding/json"
 	"fmt"
+	"html/template"
 	"io"
 	"log"
 	"net/http"
@@ -51,11 +52,14 @@
 	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
 }
 
-func (s *Server) createAdminAccountForm(w http.ResponseWriter, _ *http.Request) {
-	if _, err := w.Write(indexHtml); err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
+func (s *Server) createAdminAccountForm(w http.ResponseWriter, r *http.Request) {
+	renderRegistrationForm(w, formData{})
+}
+
+type formData struct {
+	UsernameErrors []string
+	PasswordErrors []string
+	Data           createAccountReq
 }
 
 type createAccountReq struct {
@@ -69,6 +73,15 @@
 	Password string `json:"password,omitempty"`
 }
 
+type ValidationError struct {
+	Field   string `json:"field"`
+	Message string `json:"message"`
+}
+
+type ErrorResponse struct {
+	Errors []ValidationError `json:"errors"`
+}
+
 func getFormValue(v url.Values, name string) (string, error) {
 	items, ok := v[name]
 	if !ok || len(items) != 1 {
@@ -102,6 +115,18 @@
 	return req, nil
 }
 
+func renderRegistrationForm(w http.ResponseWriter, data formData) {
+	tmpl, err := template.New("create-account").Parse(string(indexHtml))
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	if err := tmpl.Execute(w, data); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+}
+
 func (s *Server) createAdminAccount(w http.ResponseWriter, r *http.Request) {
 	req, err := extractReq(r)
 	if err != nil {
@@ -126,11 +151,26 @@
 			http.Error(w, respStr, http.StatusInternalServerError)
 			return
 		}
-		// TODO(gio): better handle status code and error message
 		if resp.StatusCode != http.StatusOK {
-			var e bytes.Buffer
-			io.Copy(&e, resp.Body)
-			http.Error(w, e.String(), http.StatusInternalServerError)
+			var errResponse ErrorResponse
+			if err := json.NewDecoder(resp.Body).Decode(&errResponse); err != nil {
+				http.Error(w, "Error Decoding JSON", http.StatusInternalServerError)
+				return
+			}
+			var usernameErrors, passwordErrors []string
+			for _, err := range errResponse.Errors {
+				if err.Field == "username" {
+					usernameErrors = append(usernameErrors, err.Message)
+				}
+				if err.Field == "password" {
+					passwordErrors = append(passwordErrors, err.Message)
+				}
+			}
+			renderRegistrationForm(w, formData{
+				usernameErrors,
+				passwordErrors,
+				req,
+			})
 			return
 		}
 	}