From e42cebe4b88563eba0344aaf660c19ada3c067ea Mon Sep 17 00:00:00 2001 From: Noah Date: Sun, 21 Aug 2022 14:05:08 -0700 Subject: [PATCH] Contact Us, Feedback, and Reporting * Add the Contact page where users can contact the site admins for feedback or to report a problematic user, photo or message. * Reports go into the admin Feedback table. * Admin nav bar indicates number of unread feedbacks. * Add "Report" button to profile pages, photo cards, and the top of Direct Message threads. Misc changes: * Send emails out asynchronously for more responsive page loads. --- pkg/config/enum.go | 33 +++++ pkg/config/page_sizes.go | 1 + pkg/controller/admin/feedback.go | 162 +++++++++++++++++++++ pkg/controller/inbox/inbox.go | 12 +- pkg/controller/index/contact.go | 165 +++++++++++++++++++++ pkg/mail/mail.go | 13 +- pkg/models/feedback.go | 89 ++++++++++++ pkg/models/models.go | 1 + pkg/router/router.go | 2 + pkg/templates/template_vars.go | 15 +- web/templates/account/profile.html | 26 ++-- web/templates/admin/dashboard.html | 7 + web/templates/admin/feedback.html | 190 +++++++++++++++++++++++++ web/templates/base.html | 5 +- web/templates/contact.html | 119 ++++++++++++++++ web/templates/email/contact_admin.html | 71 +++++++++ web/templates/friend/friends.html | 6 +- web/templates/inbox/inbox.html | 15 +- web/templates/photo/gallery.html | 51 ++++--- 19 files changed, 934 insertions(+), 49 deletions(-) create mode 100644 pkg/controller/admin/feedback.go create mode 100644 pkg/controller/index/contact.go create mode 100644 pkg/models/feedback.go create mode 100644 web/templates/admin/feedback.html create mode 100644 web/templates/contact.html create mode 100644 web/templates/email/contact_admin.html diff --git a/pkg/config/enum.go b/pkg/config/enum.go index b22c173..116b976 100644 --- a/pkg/config/enum.go +++ b/pkg/config/enum.go @@ -64,4 +64,37 @@ var ( "interests", "music_movies", } + + // Choices for the Contact Us subject + ContactUsChoices = []ContactUs{ + { + Header: "Website Feedback", + Options: []Option{ + {"feedback", "Website feedback"}, + {"feature", "Make a feature request"}, + {"bug", "Report a bug or broken feature"}, + {"other", "General/miscellaneous/other"}, + }, + }, + { + Header: "Report a Problem", + Options: []Option{ + {"report.user", "Report a problematic user"}, + {"report.photo", "Report a problematic photo"}, + {"report.message", "Report a direct message conversation"}, + }, + }, + } ) + +// ContactUs choices for the subject drop-down. +type ContactUs struct { + Header string + Options []Option +} + +// Option for select boxes. +type Option struct { + Value string + Label string +} diff --git a/pkg/config/page_sizes.go b/pkg/config/page_sizes.go index bd70937..df1c39d 100644 --- a/pkg/config/page_sizes.go +++ b/pkg/config/page_sizes.go @@ -6,6 +6,7 @@ var ( PageSizeFriends = 12 PageSizeBlockList = 12 PageSizeAdminCertification = 20 + PageSizeAdminFeedback = 20 PageSizeSiteGallery = 18 PageSizeUserGallery = 18 PageSizeInboxList = 20 // sidebar list diff --git a/pkg/controller/admin/feedback.go b/pkg/controller/admin/feedback.go new file mode 100644 index 0000000..f0fe354 --- /dev/null +++ b/pkg/controller/admin/feedback.go @@ -0,0 +1,162 @@ +package admin + +import ( + "fmt" + "net/http" + "strconv" + + "git.kirsle.net/apps/gosocial/pkg/config" + "git.kirsle.net/apps/gosocial/pkg/models" + "git.kirsle.net/apps/gosocial/pkg/photo" + "git.kirsle.net/apps/gosocial/pkg/session" + "git.kirsle.net/apps/gosocial/pkg/templates" +) + +// Feedback controller (/admin/feedback) +func Feedback() http.HandlerFunc { + tmpl := templates.Must("admin/feedback.html") + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Query params. + var ( + acknowledged = r.FormValue("acknowledged") == "true" + intent = r.FormValue("intent") + visit = r.FormValue("visit") == "true" // visit the linked table ID + profile = r.FormValue("profile") == "true" // visit associated user profile + verdict = r.FormValue("verdict") + fb *models.Feedback + ) + + currentUser, err := session.CurrentUser(r) + if err != nil { + session.FlashError(w, r, "Couldn't get your current user: %s", err) + } + + // Working on a target message? + if idStr := r.FormValue("id"); idStr != "" { + if idInt, err := strconv.Atoi(idStr); err != nil { + session.FlashError(w, r, "Couldn't parse id param: %s", err) + } else { + fb, err = models.GetFeedback(uint64(idInt)) + if err != nil { + session.FlashError(w, r, "Couldn't load feedback message %d: %s", idInt, err) + } + } + } + + // Are we visiting a linked resource (via TableID)? + if fb != nil && fb.TableID > 0 && visit { + switch fb.TableName { + case "users": + user, err := models.GetUser(fb.TableID) + if err != nil { + session.FlashError(w, r, "Couldn't visit user %d: %s", fb.TableID, err) + } else { + templates.Redirect(w, "/u/"+user.Username) + return + } + case "photos": + pic, err := models.GetPhoto(fb.TableID) + if err != nil { + session.FlashError(w, r, "Couldn't get photo %d: %s", fb.TableID, err) + } else { + // Going to the user's profile page? + if profile { + user, err := models.GetUser(pic.UserID) + if err != nil { + session.FlashError(w, r, "Couldn't visit user %d: %s", fb.TableID, err) + } else { + templates.Redirect(w, "/u/"+user.Username) + return + } + } + + // Direct link to the photo. + templates.Redirect(w, photo.URLPath(pic.Filename)) + return + } + case "messages": + // To read this message we will need to impersonate the reporter. + user, err := models.GetUser(fb.UserID) + if err != nil { + session.FlashError(w, r, "Couldn't get reporting user ID %d: %s", fb.UserID, err) + } else { + if err := session.ImpersonateUser(w, r, user, currentUser); err != nil { + session.FlashError(w, r, "Couldn't impersonate user: %s", err) + } else { + // Redirect to the thread. + session.Flash(w, r, "NOTICE: You are now impersonating %s to view their inbox.", user.Username) + templates.Redirect(w, fmt.Sprintf("/messages/read/%d", fb.TableID)) + return + } + } + default: + session.FlashError(w, r, "Couldn't visit TableID %s/%d: not a supported TableName", fb.TableName, fb.TableID) + } + } + + // Are we (un)acknowledging a message? + if r.Method == http.MethodPost { + if fb == nil { + session.FlashError(w, r, "Missing feedback ID for this POST!") + } else { + switch verdict { + case "acknowledge": + fb.Acknowledged = true + if err := fb.Save(); err != nil { + session.FlashError(w, r, "Couldn't save message: %s", err) + } else { + session.Flash(w, r, "Message acknowledged!") + } + case "unacknowledge": + fb.Acknowledged = false + if err := fb.Save(); err != nil { + session.FlashError(w, r, "Couldn't save message: %s", err) + } else { + session.Flash(w, r, "Message acknowledged!") + } + default: + session.FlashError(w, r, "Unsupported verdict: %s", verdict) + } + } + + templates.Redirect(w, r.URL.Path) + return + } + + // Get the feedback. + pager := &models.Pagination{ + Page: 1, + PerPage: config.PageSizeAdminFeedback, + Sort: "updated_at desc", + } + pager.ParsePage(r) + page, err := models.PaginateFeedback(acknowledged, intent, pager) + if err != nil { + session.FlashError(w, r, "Couldn't load feedback from DB: %s", err) + } + + // Map user IDs. + var userIDs = []uint64{} + for _, p := range page { + if p.UserID > 0 { + userIDs = append(userIDs, p.UserID) + } + } + userMap, err := models.MapUsers(userIDs) + if err != nil { + session.FlashError(w, r, "Couldn't map user IDs: %s", err) + } + + var vars = map[string]interface{}{ + "Intent": intent, + "Acknowledged": acknowledged, + "Feedback": page, + "UserMap": userMap, + "Pager": pager, + } + if err := tmpl.Execute(w, r, vars); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) +} diff --git a/pkg/controller/inbox/inbox.go b/pkg/controller/inbox/inbox.go index 82191b0..af3168d 100644 --- a/pkg/controller/inbox/inbox.go +++ b/pkg/controller/inbox/inbox.go @@ -28,11 +28,14 @@ func Inbox() http.HandlerFunc { var showSent = r.FormValue("box") == "sent" // Are we reading a specific message? - var viewThread []*models.Message - var threadPager *models.Pagination - var composeToUsername string + var ( + viewThread []*models.Message + threadPager *models.Pagination + composeToUsername string + msgId int + ) if uri := ReadURLRegexp.FindStringSubmatch(r.URL.Path); uri != nil { - msgId, _ := strconv.Atoi(uri[1]) + msgId, _ = strconv.Atoi(uri[1]) if msg, err := models.GetMessage(uint64(msgId)); err != nil { session.FlashError(w, r, "Message not found.") templates.Redirect(w, "/messages") @@ -127,6 +130,7 @@ func Inbox() http.HandlerFunc { "ViewThread": viewThread, // nil on inbox page "ThreadPager": threadPager, "ReplyTo": composeToUsername, + "MessageID": msgId, } if err := tmpl.Execute(w, r, vars); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/pkg/controller/index/contact.go b/pkg/controller/index/contact.go new file mode 100644 index 0000000..e13ca24 --- /dev/null +++ b/pkg/controller/index/contact.go @@ -0,0 +1,165 @@ +package index + +import ( + "fmt" + "html/template" + "net/http" + "strconv" + + "git.kirsle.net/apps/gosocial/pkg/config" + "git.kirsle.net/apps/gosocial/pkg/log" + "git.kirsle.net/apps/gosocial/pkg/mail" + "git.kirsle.net/apps/gosocial/pkg/markdown" + "git.kirsle.net/apps/gosocial/pkg/models" + "git.kirsle.net/apps/gosocial/pkg/session" + "git.kirsle.net/apps/gosocial/pkg/templates" +) + +// Contact or report a problem. +func Contact() http.HandlerFunc { + tmpl := templates.Must("contact.html") + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Query and form POST parameters. + var ( + intent = r.FormValue("intent") + subject = r.FormValue("subject") + title = "Contact Us" + message = r.FormValue("message") + replyTo = r.FormValue("email") + tableID int + tableName string + tableLabel string // front-end user feedback about selected report item + messageRequired = true // unless we have a table ID to work with + success = "Thank you for your feedback! Your message has been delivered to the website administrators." + ) + + // For report intents: ID of the user, photo, message, etc. + tableID, _ = strconv.Atoi(r.FormValue("id")) + if tableID > 0 { + messageRequired = false + } + + // In what context is the ID given? + if subject != "" && tableID > 0 { + switch subject { + case "report.user": + tableName = "users" + if user, err := models.GetUser(uint64(tableID)); err == nil { + tableLabel = fmt.Sprintf(`User account "%s"`, user.Username) + } else { + log.Error("/contact: couldn't produce table label for user %d: %s", tableID, err) + } + case "report.photo": + tableName = "photos" + + // Find this photo and the user associated. + if pic, err := models.GetPhoto(uint64(tableID)); err == nil { + if user, err := models.GetUser(pic.UserID); err == nil { + tableLabel = fmt.Sprintf(`A profile photo of user account "%s"`, user.Username) + } else { + log.Error("/contact: couldn't produce table label for user %d: %s", tableID, err) + } + } else { + log.Error("/contact: couldn't produce table label for photo %d: %s", tableID, err) + } + case "report.message": + tableName = "messages" + tableLabel = "Direct Message conversation" + } + } + + // On POST: take what we have now and email the admins. + if r.Method == http.MethodPost { + // Look up the current user, in case logged in. + currentUser, err := session.CurrentUser(r) + if err == nil { + replyTo = currentUser.Email + } + + // Store feedback in the database. + fb := &models.Feedback{ + Intent: intent, + Subject: subject, + Message: message, + TableName: tableName, + TableID: uint64(tableID), + } + + if currentUser != nil && currentUser.ID > 0 { + fb.UserID = currentUser.ID + } else if replyTo != "" { + fb.ReplyTo = replyTo + } + + if err := models.CreateFeedback(fb); err != nil { + session.FlashError(w, r, "Couldn't save feedback: %s", err) + templates.Redirect(w, r.URL.Path) + return + } + + // Email the admins. + if err := mail.Send(mail.Message{ + To: config.Current.AdminEmail, + Subject: "User Feedback: " + title, + Template: "email/contact_admin.html", + Data: map[string]interface{}{ + "Title": title, + "Intent": intent, + "Subject": subject, + "Message": template.HTML(markdown.Render(message)), + "TableName": tableName, + "TableID": tableID, + "CurrentUser": currentUser, + "ReplyTo": replyTo, + "BaseURL": config.Current.BaseURL, + "AdminURL": config.Current.BaseURL + "/admin/feedback", + }, + }); err != nil { + log.Error("/contact page: couldn't send email: %s", err) + } + + session.Flash(w, r, success) + templates.Redirect(w, r.URL.Path) + return + } + + // Default intent = contact + if intent == "report" { + title = "Report a Problem" + } else { + intent = "contact" + } + + // Validate the subject. + if subject != "" { + var found bool + for _, group := range config.ContactUsChoices { + for _, opt := range group.Options { + if opt.Value == subject { + found = true + break + } + } + } + + if !found { + subject = "" + } + } + + var vars = map[string]interface{}{ + "Intent": intent, + "TableID": tableID, + "TableLabel": tableLabel, + "Subject": subject, + "PageTitle": title, + "Subjects": config.ContactUsChoices, + "Message": message, + "MessageRequired": messageRequired, + } + if err := tmpl.Execute(w, r, vars); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) +} diff --git a/pkg/mail/mail.go b/pkg/mail/mail.go index eb06d04..54c9f1a 100644 --- a/pkg/mail/mail.go +++ b/pkg/mail/mail.go @@ -77,13 +77,14 @@ func Send(msg Message) error { m.SetBody("text/plain", plaintext) m.AddAlternative("text/html", html.String()) - // Deliver. - d := gomail.NewDialer(conf.Host, conf.Port, conf.Username, conf.Password) - + // Deliver asynchronously. log.Info("mail.Send: %s (%s) to %s", msg.Subject, msg.Template, msg.To) - if err := d.DialAndSend(m); err != nil { - log.Error("mail.Send: %s", err.Error()) - } + d := gomail.NewDialer(conf.Host, conf.Port, conf.Username, conf.Password) + go func() { + if err := d.DialAndSend(m); err != nil { + log.Error("mail.Send: %s", err.Error()) + } + }() return nil } diff --git a/pkg/models/feedback.go b/pkg/models/feedback.go new file mode 100644 index 0000000..2b05c85 --- /dev/null +++ b/pkg/models/feedback.go @@ -0,0 +1,89 @@ +package models + +import ( + "strings" + "time" + + "git.kirsle.net/apps/gosocial/pkg/log" +) + +// Feedback table for Contact Us & Reporting to admins. +type Feedback struct { + ID uint64 `gorm:"primaryKey"` + UserID uint64 `gorm:"index"` // if logged-in user posted this + Acknowledged bool `gorm:"index"` // admin dashboard "read" status + Intent string + Subject string + Message string + TableName string + TableID uint64 + ReplyTo string // logged-out user may leave their email for reply + CreatedAt time.Time + UpdatedAt time.Time +} + +// GetFeedback by ID. +func GetFeedback(id uint64) (*Feedback, error) { + m := &Feedback{} + result := DB.First(&m, id) + return m, result.Error +} + +// CountUnreadFeedback gets the count of unacknowledged feedback for admins. +func CountUnreadFeedback() int64 { + query := DB.Where( + "acknowledged = ?", + false, + ) + + var count int64 + result := query.Model(&Feedback{}).Count(&count) + if result.Error != nil { + log.Error("models.CountUnreadFeedback: %s", result.Error) + } + return count +} + +// PaginateFeedback +func PaginateFeedback(acknowledged bool, intent string, pager *Pagination) ([]*Feedback, error) { + var ( + fb = []*Feedback{} + wheres = []string{} + placeholders = []interface{}{} + ) + + wheres = append(wheres, "acknowledged = ?") + placeholders = append(placeholders, acknowledged) + + if intent != "" { + wheres = append(wheres, "intent = ?") + placeholders = append(placeholders, intent) + } + + query := DB.Where( + strings.Join(wheres, " AND "), + placeholders..., + ).Order( + pager.Sort, + ) + + query.Model(&Feedback{}).Count(&pager.Total) + + result := query.Offset( + pager.GetOffset(), + ).Limit(pager.PerPage).Find(&fb) + + return fb, result.Error +} + +// CreateFeedback saves a new Feedback row to the DB. +func CreateFeedback(fb *Feedback) error { + result := DB.Create(fb) + return result.Error +} + +// Save Feedback. +func (fb *Feedback) Save() error { + result := DB.Save(fb) + return result.Error +} diff --git a/pkg/models/models.go b/pkg/models/models.go index c44c956..01a8ea3 100644 --- a/pkg/models/models.go +++ b/pkg/models/models.go @@ -15,4 +15,5 @@ func AutoMigrate() { DB.AutoMigrate(&Message{}) DB.AutoMigrate(&Friend{}) DB.AutoMigrate(&Block{}) + DB.AutoMigrate(&Feedback{}) } diff --git a/pkg/router/router.go b/pkg/router/router.go index 29cd614..b1f6dfd 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -25,6 +25,7 @@ func New() http.Handler { mux.HandleFunc("/faq", index.FAQ()) mux.HandleFunc("/tos", index.TOS()) mux.HandleFunc("/privacy", index.Privacy()) + mux.HandleFunc("/contact", index.Contact()) mux.HandleFunc("/login", account.Login()) mux.HandleFunc("/logout", account.Logout()) mux.HandleFunc("/signup", account.Signup()) @@ -57,6 +58,7 @@ func New() http.Handler { // Admin endpoints. mux.Handle("/admin", middleware.AdminRequired(admin.Dashboard())) mux.Handle("/admin/photo/certification", middleware.AdminRequired(photo.AdminCertification())) + mux.Handle("/admin/feedback", middleware.AdminRequired(admin.Feedback())) mux.Handle("/admin/user-action", middleware.AdminRequired(admin.UserActions())) // JSON API endpoints. diff --git a/pkg/templates/template_vars.go b/pkg/templates/template_vars.go index 2b1aaa0..9472d63 100644 --- a/pkg/templates/template_vars.go +++ b/pkg/templates/template_vars.go @@ -32,6 +32,7 @@ func MergeUserVars(r *http.Request, m map[string]interface{}) { m["NavFriendRequests"] = 0 m["NavAdminNotifications"] = 0 // total count of admin notifications for nav m["NavCertificationPhotos"] = 0 // admin indicator for certification photos + m["NavAdminFeedback"] = 0 // admin indicator for unread feedback m["SessionImpersonated"] = false if r == nil { @@ -60,10 +61,16 @@ func MergeUserVars(r *http.Request, m map[string]interface{}) { // Are we admin? if user.IsAdmin { - // Any pending certification photos? - m["NavCertificationPhotos"] = models.CountCertificationPhotosNeedingApproval() + // Any pending certification photos or feedback? + var ( + certPhotos = models.CountCertificationPhotosNeedingApproval() + feedback = models.CountUnreadFeedback() + ) + m["NavCertificationPhotos"] = certPhotos + m["NavAdminFeedback"] = feedback + + // Total notification count for admin actions. + m["NavAdminNotifications"] = certPhotos + feedback } } - - m["NavAdminNotifications"] = m["NavCertificationPhotos"] } diff --git a/web/templates/account/profile.html b/web/templates/account/profile.html index bc11020..f51f2a6 100644 --- a/web/templates/account/profile.html +++ b/web/templates/account/profile.html @@ -120,7 +120,7 @@ -
+ +
-->
@@ -159,6 +148,17 @@
+
+ + + + + + Report + + +
+ diff --git a/web/templates/admin/dashboard.html b/web/templates/admin/dashboard.html index cebfec7..ed28c30 100644 --- a/web/templates/admin/dashboard.html +++ b/web/templates/admin/dashboard.html @@ -29,6 +29,13 @@ {{if .NavCertificationPhotos}}{{.NavCertificationPhotos}}{{end}} +
  • + + + Feedback & User Reports + {{if .NavAdminFeedback}}{{.NavAdminFeedback}}{{end}} + +
  • diff --git a/web/templates/admin/feedback.html b/web/templates/admin/feedback.html new file mode 100644 index 0000000..24d6cc8 --- /dev/null +++ b/web/templates/admin/feedback.html @@ -0,0 +1,190 @@ +{{define "title"}}Admin - Feedback & User Reports{{end}} +{{define "content"}} +{{$Root := .}} +
    +
    +
    +
    +

    + Feedback & User Reports +

    +
    +
    +
    + +
    +
    +
    + There {{Pluralize64 .Pager.Total "is" "are"}} {{.Pager.Total}} + {{if .Acknowledged}}acknowledged{{else}}unread{{end}} + {{if eq .Intent "report"}}report{{else}}feedback message{{end}}{{Pluralize64 .Pager.Total}}. +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + + {{if .Pager}} + + {{end}} + +
    + {{range .Feedback}} +
    + {{$User := $Root.UserMap.Get .UserID}} +
    + {{InputCSRF}} + + +
    +
    +

    + {{if eq .Intent "report"}} + + Report: {{.Subject}} + {{else}} + + Contact: {{.Subject}} + {{end}} +

    +
    +
    + + + + + + + + + + + + + + + + + + +
    + Intent: + {{.Intent}}
    + Subject: + {{.Subject}}
    + Table: + + {{if eq .TableName ""}} + n/a + {{if ne .TableID 0}} - {{.TableID}}{{end}} + {{else if eq .TableName "users"}} + Users: {{.TableID}} + + {{else if eq .TableName "photos"}} + Photos: {{.TableID}} + + + {{else if eq .TableName "messages"}} + Messages: {{.TableID}} + + {{else}} + {{.TableName}}: {{.TableID}} + + {{end}} +
    + Reply To: + + {{if $User}} + {{$User.Username}} + {{else if ne .ReplyTo ""}} + {{.ReplyTo}} + {{else}} + n/a + {{end}} +
    + +
    + {{if eq .Message ""}} +

    No message attached.

    + {{else}} + {{ToMarkdown .Message}} + {{end}} +
    + +
    +
    + {{if not .Acknowledged}} + + {{end}} + + {{if .Acknowledged}} + + {{end}} +
    +
    +
    +
    + {{end}} +
    +
    + +
    +{{end}} \ No newline at end of file diff --git a/web/templates/base.html b/web/templates/base.html index 4030a71..6a120b6 100644 --- a/web/templates/base.html +++ b/web/templates/base.html @@ -92,14 +92,11 @@ Privacy Policy - - Jobs - Contact - + Report an issue diff --git a/web/templates/contact.html b/web/templates/contact.html new file mode 100644 index 0000000..58fec7f --- /dev/null +++ b/web/templates/contact.html @@ -0,0 +1,119 @@ +{{define "title"}}{{.PageTitle}}{{end}} +{{define "content"}} +
    +
    +
    +
    +

    + {{.PageTitle}} +

    +
    +
    +
    + +
    +
    +
    + +
    + +
    + +
    + {{InputCSRF}} + + + +

    + You may use this form to contact the site administrators to provide + feedback, criticism, or to report a problem you have found on the + site such as inappropriate content posted by one of our members. +

    + +
    + +
    + {{$Subject := .Subject}} + +
    + + + {{if ne .Subject ""}}{{end}} +
    + + + {{if ne .TableLabel ""}} +
    + +

    {{.TableLabel}}

    +
    + {{end}} + +
    + + + {{if not .MessageRequired}} +

    + Write a description of the problem (optional). +

    + {{end}} +
    + + {{if not .LoggedIn}} +
    + + +

    + Optional; you are not logged in to an account so you MAY leave us + a reply-to email address if you would like a response to your feedback. +

    +
    + {{end}} + +
    + +
    +
    + +
    +
    + +
    +
    +
    + +
    + + +{{end}} \ No newline at end of file diff --git a/web/templates/email/contact_admin.html b/web/templates/email/contact_admin.html new file mode 100644 index 0000000..d13b060 --- /dev/null +++ b/web/templates/email/contact_admin.html @@ -0,0 +1,71 @@ +{{define "content"}} + + + + +

    User Feedback: {{.Data.Title}}

    + +

    + Dear website administrators, +

    + +

    + A user has posted a message to you via the Contact Us form. This message + is viewable on your admin dashboard and the details are also copied below: +

    + + + +

    + The user's message was as follows: +

    + +
    + + {{if ne .Data.Message ""}} + {{.Data.Message}} + {{else}} + No message attached. + {{end}} + +
    + +

    + To view this message on the admin dashboard, please visit: + {{.Data.AdminURL}} +

    + +

    + This is an automated e-mail; do not reply to this message. +

    + + +{{end}} \ No newline at end of file diff --git a/web/templates/friend/friends.html b/web/templates/friend/friends.html index 11c4797..ec24bb6 100644 --- a/web/templates/friend/friends.html +++ b/web/templates/friend/friends.html @@ -72,15 +72,19 @@
    -

    {{.NameOrUsername}}

    +

    + {{.NameOrUsername}} +

    {{.Username}} diff --git a/web/templates/inbox/inbox.html b/web/templates/inbox/inbox.html index 6503c04..a4c0997 100644 --- a/web/templates/inbox/inbox.html +++ b/web/templates/inbox/inbox.html @@ -33,7 +33,20 @@ - + +

    +
    + +
    + +
    +
    diff --git a/web/templates/photo/gallery.html b/web/templates/photo/gallery.html index 0fad3ad..8e93ef5 100644 --- a/web/templates/photo/gallery.html +++ b/web/templates/photo/gallery.html @@ -55,16 +55,14 @@ {{define "card-footer"}} - + + + Edit + + + + Delete + {{end}} @@ -246,9 +244,20 @@ {{template "card-body" .}}
    - {{if or $Root.IsOwnPhotos $Root.CurrentUser.IsAdmin}} - {{template "card-footer" .}} - {{end}} + +
    + {{if or $Root.IsOwnPhotos $Root.CurrentUser.IsAdmin}} + {{template "card-footer" .}} + {{end}} + + {{if not $Root.IsOwnPhotos}} + + + Report + + {{end}} +
    +
    {{end}} @@ -305,9 +314,19 @@ {{template "card-body" .}} - {{if or $Root.IsOwnPhotos $Root.CurrentUser.IsAdmin}} - {{template "card-footer" .}} - {{end}} + + {{end}}