memberships: modal for errors (#133)

* unpolished version of error modal rendering

* rework of html files. template implemented

* new html files

* minor fixes.

* minor fixes

* title changes
diff --git a/core/auth/memberships/main.go b/core/auth/memberships/main.go
index f8f2cff..7cb7538 100644
--- a/core/auth/memberships/main.go
+++ b/core/auth/memberships/main.go
@@ -9,6 +9,7 @@
 	"html/template"
 	"log"
 	"net/http"
+	"net/url"
 	"regexp"
 	"strings"
 
@@ -23,11 +24,8 @@
 var apiPort = flag.Int("api-port", 8081, "Port to listen on for API requests")
 var dbPath = flag.String("db-path", "memberships.db", "Path to SQLite file")
 
-//go:embed index.html
-var indexHTML string
-
-//go:embed group.html
-var groupHTML string
+//go:embed memberships-tmpl/*
+var tmpls embed.FS
 
 //go:embed static
 var staticResources embed.FS
@@ -563,6 +561,34 @@
 	return true, nil
 }
 
+type templates struct {
+	group *template.Template
+	user  *template.Template
+}
+
+func parseTemplates(fs embed.FS) (templates, error) {
+	base, err := template.ParseFS(fs, "memberships-tmpl/base.html")
+	if err != nil {
+		return templates{}, err
+	}
+	parse := func(path string) (*template.Template, error) {
+		if b, err := base.Clone(); err != nil {
+			return nil, err
+		} else {
+			return b.ParseFS(fs, path)
+		}
+	}
+	user, err := parse("memberships-tmpl/user.html")
+	if err != nil {
+		return templates{}, err
+	}
+	group, err := parse("memberships-tmpl/group.html")
+	if err != nil {
+		return templates{}, err
+	}
+	return templates{group, user}, nil
+}
+
 func (s *Server) homePageHandler(w http.ResponseWriter, r *http.Request) {
 	loggedInUser, err := getLoggedInUser(r)
 	if err != nil {
@@ -578,6 +604,7 @@
 		http.Error(w, "User Not Logged In", http.StatusUnauthorized)
 		return
 	}
+	errorMsg := r.URL.Query().Get("errorMessage")
 	vars := mux.Vars(r)
 	user := strings.ToLower(vars["username"])
 	// TODO(dtabidze): should check if username exists or not.
@@ -592,11 +619,6 @@
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
-	tmpl, err := template.New("index").Parse(indexHTML)
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
 	transitiveGroups, err := s.store.GetAllTransitiveGroupsForUser(user)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -608,15 +630,23 @@
 		TransitiveGroups []Group
 		LoggedInUserPage bool
 		CurrentUser      string
+		ErrorMessage     string
+		AdditionalCSS    bool
 	}{
 		OwnerGroups:      ownerGroups,
 		MembershipGroups: membershipGroups,
 		TransitiveGroups: transitiveGroups,
 		LoggedInUserPage: loggedInUserPage,
 		CurrentUser:      user,
+		ErrorMessage:     errorMsg,
+		AdditionalCSS:    false,
 	}
-	w.Header().Set("Content-Type", "text/html")
-	if err := tmpl.Execute(w, data); err != nil {
+	templates, err := parseTemplates(tmpls)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	if err := templates.user.Execute(w, data); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
@@ -639,12 +669,16 @@
 	var group Group
 	group.Name = r.PostFormValue("group-name")
 	if err := isValidGroupName(group.Name); err != nil {
-		http.Error(w, err.Error(), http.StatusBadRequest)
+		// http.Error(w, err.Error(), http.StatusBadRequest)
+		redirectURL := fmt.Sprintf("/user/%s?errorMessage=%s", loggedInUser, url.QueryEscape(err.Error()))
+		http.Redirect(w, r, redirectURL, http.StatusFound)
 		return
 	}
 	group.Description = r.PostFormValue("description")
 	if err := s.store.CreateGroup(loggedInUser, group); err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
+		// http.Error(w, err.Error(), http.StatusInternalServerError)
+		redirectURL := fmt.Sprintf("/user/%s?errorMessage=%s", loggedInUser, url.QueryEscape(err.Error()))
+		http.Redirect(w, r, redirectURL, http.StatusFound)
 		return
 	}
 	http.Redirect(w, r, "/", http.StatusSeeOther)
@@ -656,6 +690,7 @@
 		http.Error(w, "User Not Logged In", http.StatusUnauthorized)
 		return
 	}
+	errorMsg := r.URL.Query().Get("errorMessage")
 	vars := mux.Vars(r)
 	groupName := vars["group-name"]
 	exists, err := s.store.DoesGroupExist(groupName)
@@ -664,11 +699,11 @@
 		return
 	}
 	if !exists {
-		errorMsg := fmt.Sprintf("group with the name '%s' not found", groupName)
+		errorMsg = fmt.Sprintf("group with the name '%s' not found", groupName)
 		http.Error(w, errorMsg, http.StatusNotFound)
 		return
 	}
-	tmpl, err := template.New("group").Parse(groupHTML)
+	// tmpl, err := template.New("group").Parse(groupHTML)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
@@ -711,6 +746,7 @@
 		AvailableGroups  []string
 		TransitiveGroups []Group
 		ChildGroups      []Group
+		ErrorMessage     string
 	}{
 		GroupName:        groupName,
 		Description:      description,
@@ -719,8 +755,14 @@
 		AvailableGroups:  availableGroups,
 		TransitiveGroups: transitiveGroups,
 		ChildGroups:      childGroups,
+		ErrorMessage:     errorMsg,
 	}
-	if err := tmpl.Execute(w, data); err != nil {
+	templates, err := parseTemplates(tmpls)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	if err := templates.group.Execute(w, data); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
@@ -750,7 +792,8 @@
 		}
 		err := s.store.RemoveFromGroupToGroup(parentGroup, childGroup)
 		if err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
+			redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", parentGroup, url.QueryEscape(err.Error()))
+			http.Redirect(w, r, redirectURL, http.StatusFound)
 			return
 		}
 		http.Redirect(w, r, "/group/"+parentGroup, http.StatusSeeOther)
@@ -778,7 +821,8 @@
 		}
 		err := s.store.RemoveUserFromTable(username, groupName, tableName)
 		if err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
+			redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", groupName, url.QueryEscape(err.Error()))
+			http.Redirect(w, r, redirectURL, http.StatusFound)
 			return
 		}
 		http.Redirect(w, r, "/group/"+groupName, http.StatusSeeOther)
@@ -806,7 +850,8 @@
 		}
 		err := s.store.RemoveUserFromTable(username, groupName, tableName)
 		if err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
+			redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", groupName, url.QueryEscape(err.Error()))
+			http.Redirect(w, r, redirectURL, http.StatusFound)
 			return
 		}
 		http.Redirect(w, r, "/group/"+groupName, http.StatusSeeOther)
@@ -853,7 +898,8 @@
 		return
 	}
 	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
+		redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", groupName, url.QueryEscape(err.Error()))
+		http.Redirect(w, r, redirectURL, http.StatusFound)
 		return
 	}
 	http.Redirect(w, r, "/group/"+groupName, http.StatusSeeOther)
@@ -886,7 +932,8 @@
 		return
 	}
 	if err := s.store.AddChildGroup(parentGroup, childGroup); err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
+		redirectURL := fmt.Sprintf("/group/%s?errorMessage=%s", parentGroup, url.QueryEscape(err.Error()))
+		http.Redirect(w, r, redirectURL, http.StatusFound)
 		return
 	}
 	http.Redirect(w, r, "/group/"+parentGroup, http.StatusSeeOther)