diff --git a/internal/auth/auth.go b/internal/auth/auth.go
index 703cea5..9d27c2f 100644
--- a/internal/auth/auth.go
+++ b/internal/auth/auth.go
@@ -65,6 +65,10 @@ func (s *Service) Login(ctx context.Context, emailAddr, password string) (*model
return nil, fmt.Errorf("please verify your email before logging in")
}
+ if !user.Approved {
+ return nil, fmt.Errorf("your account is pending admin approval")
+ }
+
return &user, nil
}
@@ -91,7 +95,7 @@ func (s *Service) DestroySession(r *http.Request, w http.ResponseWriter) error {
return s.store.Save(r, w, session)
}
-func (s *Service) CreateUserWithPassword(ctx context.Context, emailAddr, password, name string, verified bool) (*models.User, error) {
+func (s *Service) CreateUserWithPassword(ctx context.Context, emailAddr, password, name string, verified bool, approved bool) (*models.User, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return nil, fmt.Errorf("hash password: %w", err)
@@ -103,6 +107,7 @@ func (s *Service) CreateUserWithPassword(ctx context.Context, emailAddr, passwor
PasswordHash: &hashStr,
Name: name,
EmailVerified: verified,
+ Approved: approved,
}
if err := s.db.WithContext(ctx).Create(&user).Error; err != nil {
diff --git a/internal/auth/oauth.go b/internal/auth/oauth.go
index 5f92d0e..afab5ac 100644
--- a/internal/auth/oauth.go
+++ b/internal/auth/oauth.go
@@ -119,16 +119,19 @@ func (s *Service) FindOrCreateOAuthUser(ctx context.Context, provider string, in
// Try to find existing user by email
var user models.User
+ isNewUser := false
if err := s.db.WithContext(ctx).Where("email = ?", info.Email).First(&user).Error; err != nil {
- // Create new user
+ // Create new user — approved is false, requires admin approval
user = models.User{
Email: info.Email,
Name: info.Name,
EmailVerified: true,
+ Approved: false,
}
if err := s.db.WithContext(ctx).Create(&user).Error; err != nil {
return nil, fmt.Errorf("create user: %w", err)
}
+ isNewUser = true
}
// Link OAuth account
@@ -148,5 +151,14 @@ func (s *Service) FindOrCreateOAuthUser(ctx context.Context, provider string, in
user.EmailVerified = true
}
+ // New OAuth users need admin approval
+ if isNewUser {
+ return nil, fmt.Errorf("your account has been created and is pending admin approval")
+ }
+
+ if !user.Approved {
+ return nil, fmt.Errorf("your account is pending admin approval")
+ }
+
return &user, nil
}
diff --git a/internal/email/email.go b/internal/email/email.go
index 95f5849..9864b60 100644
--- a/internal/email/email.go
+++ b/internal/email/email.go
@@ -85,6 +85,26 @@ func (c *Client) SendTicketClosedNotification(to, name, ticketTitle, ticketID st
return err
}
+func (c *Client) SendAccountApprovedEmail(to, name string) error {
+ if c.server == nil {
+ return fmt.Errorf("email client not configured")
+ }
+
+ loginURL := fmt.Sprintf("%s/login", c.baseURL)
+ htmlBody := renderAccountApprovedEmail(name, loginURL)
+ textBody := fmt.Sprintf("Hi %s,\n\nYour account has been approved! You can now log in at %s.", name, loginURL)
+
+ _, err := c.server.SendEmail(context.Background(), postmark.Email{
+ From: c.fromEmail,
+ To: to,
+ Subject: "Your account has been approved",
+ HTMLBody: htmlBody,
+ TextBody: textBody,
+ Tag: "account-approved",
+ })
+ return err
+}
+
func (c *Client) SendWelcomeEmail(to, name, tempPassword string) error {
if c.server == nil {
return fmt.Errorf("email client not configured")
diff --git a/internal/email/templates.go b/internal/email/templates.go
index 9b693b0..decb0d0 100644
--- a/internal/email/templates.go
+++ b/internal/email/templates.go
@@ -51,6 +51,16 @@ func renderTicketClosedEmail(name, ticketTitle, ticketURL string) string {
If you believe the issue is not fully resolved, you can add a comment on the ticket page.
`, name, ticketTitle, ticketURL))
}
+func renderAccountApprovedEmail(name, loginURL string) string {
+ return emailWrapper(fmt.Sprintf(`
+Your account has been approved
+Hi %s,
+Your account request has been approved. You can now log in and start creating tickets.
+
+ Log In
+
`, name, loginURL))
+}
+
func renderWelcomeEmail(name, email, tempPassword, loginURL string) string {
return emailWrapper(fmt.Sprintf(`
Welcome!
diff --git a/internal/forgejo/client.go b/internal/forgejo/client.go
index 53c6929..a7fa626 100644
--- a/internal/forgejo/client.go
+++ b/internal/forgejo/client.go
@@ -28,8 +28,14 @@ func NewClient(baseURL, apiToken string) *Client {
}
type CreateIssueRequest struct {
- Title string `json:"title"`
- Body string `json:"body"`
+ Title string `json:"title"`
+ Body string `json:"body"`
+ Labels []int64 `json:"labels,omitempty"`
+}
+
+type Label struct {
+ ID int64 `json:"id"`
+ Name string `json:"name"`
}
type Issue struct {
@@ -55,6 +61,62 @@ func GenerateWebhookSecret() (string, error) {
return hex.EncodeToString(b), nil
}
+func (c *Client) GetOrCreateLabel(owner, repo, labelName, color string) (*Label, error) {
+ // Try to find existing label
+ listURL := fmt.Sprintf("%s/api/v1/repos/%s/%s/labels", c.baseURL, owner, repo)
+ httpReq, err := http.NewRequest("GET", listURL, nil)
+ if err != nil {
+ return nil, err
+ }
+ httpReq.Header.Set("Authorization", "token "+c.apiToken)
+
+ resp, err := c.httpClient.Do(httpReq)
+ if err != nil {
+ return nil, fmt.Errorf("forgejo API request failed: %w", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode == http.StatusOK {
+ var labels []Label
+ if err := json.NewDecoder(resp.Body).Decode(&labels); err == nil {
+ for _, l := range labels {
+ if l.Name == labelName {
+ return &l, nil
+ }
+ }
+ }
+ }
+
+ // Create the label
+ createBody, _ := json.Marshal(map[string]string{
+ "name": labelName,
+ "color": color,
+ })
+ httpReq, err = http.NewRequest("POST", listURL, bytes.NewReader(createBody))
+ if err != nil {
+ return nil, err
+ }
+ httpReq.Header.Set("Content-Type", "application/json")
+ httpReq.Header.Set("Authorization", "token "+c.apiToken)
+
+ resp2, err := c.httpClient.Do(httpReq)
+ if err != nil {
+ return nil, fmt.Errorf("forgejo API request failed: %w", err)
+ }
+ defer resp2.Body.Close()
+
+ if resp2.StatusCode != http.StatusCreated {
+ respBody, _ := io.ReadAll(resp2.Body)
+ return nil, fmt.Errorf("forgejo API returned %d: %s", resp2.StatusCode, string(respBody))
+ }
+
+ var label Label
+ if err := json.NewDecoder(resp2.Body).Decode(&label); err != nil {
+ return nil, err
+ }
+ return &label, nil
+}
+
func (c *Client) CreateIssue(owner, repo string, req CreateIssueRequest) (*Issue, error) {
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/issues", c.baseURL, owner, repo)
diff --git a/internal/handlers/admin/routes.go b/internal/handlers/admin/routes.go
index 8b96e59..8daf838 100644
--- a/internal/handlers/admin/routes.go
+++ b/internal/handlers/admin/routes.go
@@ -33,9 +33,13 @@ func NewRouter(deps Dependencies) *gin.Engine {
userHandler := &UserHandler{deps: deps}
r.GET("/users", userHandler.List)
+ r.GET("/users/pending", userHandler.PendingList)
r.GET("/users/new", userHandler.NewForm)
r.GET("/users/:id", userHandler.Detail)
r.POST("/users", userHandler.Create)
+ r.POST("/users/:id/approve", userHandler.Approve)
+ r.POST("/users/:id/reject", userHandler.Reject)
+ r.POST("/users/:id/repos", userHandler.UpdateRepos)
ticketHandler := &TicketHandler{deps: deps}
r.GET("/tickets", ticketHandler.List)
diff --git a/internal/handlers/admin/users.go b/internal/handlers/admin/users.go
index e3a134b..067fdd5 100644
--- a/internal/handlers/admin/users.go
+++ b/internal/handlers/admin/users.go
@@ -4,6 +4,7 @@ import (
"crypto/rand"
"encoding/hex"
"net/http"
+ "net/url"
"strings"
"github.com/gin-gonic/gin"
@@ -45,9 +46,23 @@ func (h *UserHandler) Detail(c *gin.Context) {
var tickets []models.Ticket
h.deps.DB.Preload("Repo").Where("user_id = ?", user.ID).Order("created_at DESC").Limit(50).Find(&tickets)
+ // Load all repos and user's assigned repo IDs
+ var allRepos []models.Repo
+ h.deps.DB.Where("active = ?", true).Order("name ASC").Find(&allRepos)
+
+ var userRepos []models.UserRepo
+ h.deps.DB.Where("user_id = ?", user.ID).Find(&userRepos)
+
+ assignedRepoIDs := make(map[string]bool)
+ for _, ur := range userRepos {
+ assignedRepoIDs[ur.RepoID.String()] = true
+ }
+
h.deps.Renderer.Render(c.Writer, c.Request, "admin/users/detail", map[string]interface{}{
- "User": user,
- "Tickets": tickets,
+ "User": user,
+ "Tickets": tickets,
+ "AllRepos": allRepos,
+ "AssignedRepoIDs": assignedRepoIDs,
})
}
@@ -72,7 +87,7 @@ func (h *UserHandler) Create(c *gin.Context) {
rand.Read(tempPassBytes)
tempPassword := hex.EncodeToString(tempPassBytes)[:16]
- user, err := h.deps.Auth.CreateUserWithPassword(c.Request.Context(), email, tempPassword, name, true)
+ user, err := h.deps.Auth.CreateUserWithPassword(c.Request.Context(), email, tempPassword, name, true, true)
if err != nil {
if strings.Contains(err.Error(), "duplicate key") || strings.Contains(err.Error(), "unique") {
h.deps.Renderer.Render(c.Writer, c.Request, "admin/users/new", map[string]interface{}{
@@ -96,3 +111,97 @@ func (h *UserHandler) Create(c *gin.Context) {
c.Redirect(http.StatusSeeOther, "/users/"+user.ID.String())
}
+
+func (h *UserHandler) PendingList(c *gin.Context) {
+ var users []models.User
+ if err := h.deps.DB.Where("email_verified = ? AND approved = ?", true, false).Order("created_at DESC").Find(&users).Error; err != nil {
+ log.Error().Err(err).Msg("list pending users error")
+ h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusInternalServerError, "Failed to load pending users")
+ return
+ }
+
+ h.deps.Renderer.Render(c.Writer, c.Request, "admin/users/pending", map[string]interface{}{
+ "Users": users,
+ })
+}
+
+func (h *UserHandler) Approve(c *gin.Context) {
+ userID, err := uuid.Parse(c.Param("id"))
+ if err != nil {
+ h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusBadRequest, "Invalid user ID")
+ return
+ }
+
+ var user models.User
+ if err := h.deps.DB.First(&user, "id = ?", userID).Error; err != nil {
+ h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusNotFound, "User not found")
+ return
+ }
+
+ h.deps.DB.Model(&user).Update("approved", true)
+
+ if err := h.deps.EmailClient.SendAccountApprovedEmail(user.Email, user.Name); err != nil {
+ log.Error().Err(err).Msg("send approval email error")
+ }
+
+ redirectURL := "/users/pending?" + url.Values{
+ "flash": {"User " + user.Email + " has been approved"},
+ "flash_type": {"success"},
+ }.Encode()
+ c.Redirect(http.StatusSeeOther, redirectURL)
+}
+
+func (h *UserHandler) Reject(c *gin.Context) {
+ userID, err := uuid.Parse(c.Param("id"))
+ if err != nil {
+ h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusBadRequest, "Invalid user ID")
+ return
+ }
+
+ if err := h.deps.DB.Delete(&models.User{}, "id = ?", userID).Error; err != nil {
+ log.Error().Err(err).Msg("delete user error")
+ h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusInternalServerError, "Failed to reject user")
+ return
+ }
+
+ redirectURL := "/users/pending?" + url.Values{
+ "flash": {"User request has been rejected"},
+ "flash_type": {"success"},
+ }.Encode()
+ c.Redirect(http.StatusSeeOther, redirectURL)
+}
+
+func (h *UserHandler) UpdateRepos(c *gin.Context) {
+ userID, err := uuid.Parse(c.Param("id"))
+ if err != nil {
+ h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusBadRequest, "Invalid user ID")
+ return
+ }
+
+ var user models.User
+ if err := h.deps.DB.First(&user, "id = ?", userID).Error; err != nil {
+ h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusNotFound, "User not found")
+ return
+ }
+
+ // Get selected repo IDs from form
+ repoIDs := c.PostFormArray("repo_ids")
+
+ // Delete existing assignments
+ h.deps.DB.Where("user_id = ?", userID).Delete(&models.UserRepo{})
+
+ // Create new assignments
+ for _, idStr := range repoIDs {
+ repoID, err := uuid.Parse(idStr)
+ if err != nil {
+ continue
+ }
+ h.deps.DB.Create(&models.UserRepo{UserID: userID, RepoID: repoID})
+ }
+
+ redirectURL := "/users/" + userID.String() + "?" + url.Values{
+ "flash": {"Project assignments updated"},
+ "flash_type": {"success"},
+ }.Encode()
+ c.Redirect(http.StatusSeeOther, redirectURL)
+}
diff --git a/internal/handlers/public/auth.go b/internal/handlers/public/auth.go
index ffd12c1..d61a06b 100644
--- a/internal/handlers/public/auth.go
+++ b/internal/handlers/public/auth.go
@@ -112,7 +112,7 @@ func (h *AuthHandler) Register(c *gin.Context) {
}
redirectURL := "/login?" + url.Values{
- "flash": {"Please check your email to verify your account"},
+ "flash": {"Account requested! Please check your email to verify your address. After verification, an admin will review your request."},
"flash_type": {"success"},
}.Encode()
c.Redirect(http.StatusSeeOther, redirectURL)
@@ -139,7 +139,7 @@ func (h *AuthHandler) VerifyEmail(c *gin.Context) {
}
redirectURL := "/login?" + url.Values{
- "flash": {"Email verified successfully. You can now log in."},
+ "flash": {"Email verified successfully. Your account is pending admin approval."},
"flash_type": {"success"},
}.Encode()
c.Redirect(http.StatusSeeOther, redirectURL)
diff --git a/internal/handlers/public/oauth.go b/internal/handlers/public/oauth.go
index acc0186..839f4b0 100644
--- a/internal/handlers/public/oauth.go
+++ b/internal/handlers/public/oauth.go
@@ -4,6 +4,8 @@ import (
"crypto/rand"
"encoding/hex"
"net/http"
+ "net/url"
+ "strings"
"github.com/gin-gonic/gin"
"github.com/mattnite/forgejo-tickets/internal/auth"
@@ -95,6 +97,14 @@ func (h *OAuthHandler) Callback(c *gin.Context) {
user, err := h.deps.Auth.FindOrCreateOAuthUser(c.Request.Context(), provider.Name, info)
if err != nil {
+ if strings.Contains(err.Error(), "pending admin approval") {
+ redirectURL := "/login?" + url.Values{
+ "flash": {err.Error()},
+ "flash_type": {"info"},
+ }.Encode()
+ c.Redirect(http.StatusSeeOther, redirectURL)
+ return
+ }
log.Error().Err(err).Msg("find or create oauth user error")
c.String(http.StatusInternalServerError, "Authentication failed")
return
@@ -185,6 +195,14 @@ func (h *OAuthHandler) AppleCallback(c *gin.Context) {
user, err := h.deps.Auth.FindOrCreateOAuthUser(c.Request.Context(), "apple", info)
if err != nil {
+ if strings.Contains(err.Error(), "pending admin approval") {
+ redirectURL := "/login?" + url.Values{
+ "flash": {err.Error()},
+ "flash_type": {"info"},
+ }.Encode()
+ c.Redirect(http.StatusSeeOther, redirectURL)
+ return
+ }
log.Error().Err(err).Msg("find or create apple user error")
c.String(http.StatusInternalServerError, "Authentication failed")
return
diff --git a/internal/handlers/public/tickets.go b/internal/handlers/public/tickets.go
index 9644a69..a2fe8d9 100644
--- a/internal/handlers/public/tickets.go
+++ b/internal/handlers/public/tickets.go
@@ -31,8 +31,14 @@ func (h *TicketHandler) List(c *gin.Context) {
}
func (h *TicketHandler) NewForm(c *gin.Context) {
+ user := auth.CurrentUser(c)
+
var repos []models.Repo
- if err := h.deps.DB.Where("active = ?", true).Order("name ASC").Find(&repos).Error; err != nil {
+ if err := h.deps.DB.
+ Joins("JOIN user_repos ON user_repos.repo_id = repos.id").
+ Where("user_repos.user_id = ? AND repos.active = ?", user.ID, true).
+ Order("repos.name ASC").
+ Find(&repos).Error; err != nil {
log.Error().Err(err).Msg("list repos error")
h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusInternalServerError, "Failed to load products")
return
@@ -52,12 +58,23 @@ func (h *TicketHandler) Create(c *gin.Context) {
return
}
+ // Validate the user has access to this repo
+ var userRepo models.UserRepo
+ if err := h.deps.DB.Where("user_id = ? AND repo_id = ?", user.ID, repoID).First(&userRepo).Error; err != nil {
+ h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusForbidden, "You do not have access to this project")
+ return
+ }
+
title := c.PostForm("title")
description := c.PostForm("description")
if title == "" || description == "" {
var repos []models.Repo
- h.deps.DB.Where("active = ?", true).Order("name ASC").Find(&repos)
+ h.deps.DB.
+ Joins("JOIN user_repos ON user_repos.repo_id = repos.id").
+ Where("user_repos.user_id = ? AND repos.active = ?", user.ID, true).
+ Order("repos.name ASC").
+ Find(&repos)
h.deps.Renderer.Render(c.Writer, c.Request, "tickets/new", map[string]interface{}{
"Repos": repos,
"Error": "Title and description are required",
@@ -85,9 +102,19 @@ func (h *TicketHandler) Create(c *gin.Context) {
var repo models.Repo
if err := h.deps.DB.First(&repo, "id = ?", repoID).Error; err == nil {
go func() {
+ // Look up or create the "customer" label
+ var labelIDs []int64
+ label, err := h.deps.ForgejoClient.GetOrCreateLabel(repo.ForgejoOwner, repo.ForgejoRepo, "customer", "#0075ca")
+ if err != nil {
+ log.Error().Err(err).Msg("forgejo get/create label error")
+ } else {
+ labelIDs = append(labelIDs, label.ID)
+ }
+
issue, err := h.deps.ForgejoClient.CreateIssue(repo.ForgejoOwner, repo.ForgejoRepo, forgejo.CreateIssueRequest{
- Title: title,
- Body: description + "\n\n---\n*Submitted by: " + user.Email + "*",
+ Title: title,
+ Body: description + "\n\n---\n*Submitted by: " + user.Email + "*",
+ Labels: labelIDs,
})
if err != nil {
log.Error().Err(err).Msgf("forgejo create issue error for ticket %s", ticket.ID)
diff --git a/internal/models/models.go b/internal/models/models.go
index 53735e4..98a2788 100644
--- a/internal/models/models.go
+++ b/internal/models/models.go
@@ -23,13 +23,21 @@ const (
)
type User struct {
- ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid();primaryKey" json:"id"`
- Email string `gorm:"uniqueIndex;not null" json:"email"`
- PasswordHash *string `json:"-"`
- Name string `gorm:"not null" json:"name"`
- EmailVerified bool `gorm:"not null;default:false" json:"email_verified"`
- CreatedAt time.Time `gorm:"not null;default:now()" json:"created_at"`
- UpdatedAt time.Time `gorm:"not null;default:now()" json:"updated_at"`
+ ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid();primaryKey" json:"id"`
+ Email string `gorm:"uniqueIndex;not null" json:"email"`
+ PasswordHash *string `json:"-"`
+ Name string `gorm:"not null" json:"name"`
+ EmailVerified bool `gorm:"not null;default:false" json:"email_verified"`
+ Approved bool `gorm:"not null;default:false" json:"approved"`
+ CreatedAt time.Time `gorm:"not null;default:now()" json:"created_at"`
+ UpdatedAt time.Time `gorm:"not null;default:now()" json:"updated_at"`
+}
+
+type UserRepo struct {
+ UserID uuid.UUID `gorm:"type:uuid;not null;primaryKey"`
+ RepoID uuid.UUID `gorm:"type:uuid;not null;primaryKey"`
+ User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"`
+ Repo Repo `gorm:"foreignKey:RepoID;constraint:OnDelete:CASCADE"`
}
type OAuthAccount struct {
@@ -115,6 +123,7 @@ func AutoMigrate(db *gorm.DB) error {
&Ticket{},
&TicketComment{},
&EmailToken{},
+ &UserRepo{},
); err != nil {
return err
}
@@ -125,5 +134,8 @@ func AutoMigrate(db *gorm.DB) error {
// Create partial unique index for ticket forgejo issue lookup
db.Exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_tickets_repo_forgejo_issue ON tickets(repo_id, forgejo_issue_number) WHERE forgejo_issue_number IS NOT NULL")
+ // Approve all existing verified users so they aren't locked out
+ db.Exec("UPDATE users SET approved = true WHERE approved = false AND email_verified = true")
+
return nil
}
diff --git a/web/templates/layouts/admin.html b/web/templates/layouts/admin.html
index 041e073..c153dcb 100644
--- a/web/templates/layouts/admin.html
+++ b/web/templates/layouts/admin.html
@@ -8,21 +8,25 @@
-
+
+
Approved
+ {{if .User.Approved}}Yes{{else}}Pending{{end}}
+
Created
{{formatDate .User.CreatedAt}}
@@ -24,6 +28,27 @@
+Project Access
+{{if .AllRepos}}
+
+{{else}}
+No active projects available.
+{{end}}
+
Tickets
{{if .Tickets}}
diff --git a/web/templates/pages/admin/users/pending.html b/web/templates/pages/admin/users/pending.html
new file mode 100644
index 0000000..0d9547d
--- /dev/null
+++ b/web/templates/pages/admin/users/pending.html
@@ -0,0 +1,46 @@
+{{define "title"}}Pending Account Requests{{end}}
+
+{{define "content"}}
+
+
+{{with .Data}}
+{{if .Users}}
+
+
+
+
+ | Name |
+ Email |
+ Requested |
+ Actions |
+
+
+
+ {{range .Users}}
+
+ | {{.Name}} |
+ {{.Email}} |
+ {{formatDate .CreatedAt}} |
+
+
+
+
+
+ |
+
+ {{end}}
+
+
+
+{{else}}
+
No pending account requests.
+{{end}}
+{{end}}
+{{end}}
diff --git a/web/templates/pages/register.html b/web/templates/pages/register.html
index f83e463..5ea3f9e 100644
--- a/web/templates/pages/register.html
+++ b/web/templates/pages/register.html
@@ -1,8 +1,8 @@
-{{define "title"}}Register{{end}}
+{{define "title"}}Request Account{{end}}
{{define "content"}}
-
Create your account
+ Request an Account
{{with .Data}}
{{if .Error}}
@@ -42,7 +42,7 @@
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500">
-
+
diff --git a/web/templates/pages/tickets/new.html b/web/templates/pages/tickets/new.html
index 5b88676..a1c69da 100644
--- a/web/templates/pages/tickets/new.html
+++ b/web/templates/pages/tickets/new.html
@@ -22,7 +22,7 @@
{{with .Data}}
{{range .Repos}}
-
+
{{end}}
{{end}}
diff --git a/web/templates/partials/flash.html b/web/templates/partials/flash.html
index b07e473..cd530a4 100644
--- a/web/templates/partials/flash.html
+++ b/web/templates/partials/flash.html
@@ -1,6 +1,6 @@
{{define "flash"}}
{{if .Flash}}
-
+
{{if eq .Flash.Type "success"}}
{{.Flash.Message}}
diff --git a/web/templates/partials/nav.html b/web/templates/partials/nav.html
index 4b056aa..89fcc11 100644
--- a/web/templates/partials/nav.html
+++ b/web/templates/partials/nav.html
@@ -14,7 +14,7 @@
{{else}}
Login
-
Register
+
Request Account
{{end}}