diff --git a/cmd/web/handlers_guestbook.go b/cmd/web/handlers_guestbook.go
index 46668bc..1295114 100644
--- a/cmd/web/handlers_guestbook.go
+++ b/cmd/web/handlers_guestbook.go
@@ -151,11 +151,12 @@ func (app *application) getGuestbookCommentsSerialized(w http.ResponseWriter, r
 	if !website.Guestbook.Settings.IsVisible || !website.Guestbook.Settings.AllowRemoteHostAccess {
 		app.clientError(w, http.StatusForbidden)
 	}
-	comments, err := app.guestbookComments.GetAll(website.Guestbook.ID)
+	comments, err := app.guestbookComments.GetAllSerialized(website.Guestbook.ID)
 	if err != nil {
 		app.serverError(w, r, err)
 		return
 	}
+
 	b, err := json.Marshal(comments)
 	w.Write(b)
 }
@@ -244,6 +245,54 @@ func (app *application) postGuestbookCommentCreate(w http.ResponseWriter, r *htt
 	http.Redirect(w, r, fmt.Sprintf("/websites/%s/guestbook", slug), http.StatusSeeOther)
 }
 
+func (app *application) postGuestbookCommentCreateRemote(w http.ResponseWriter, r *http.Request) {
+	slug := r.PathValue("id")
+	website, err := app.websites.Get(slugToShortId(slug))
+	if err != nil {
+		if errors.Is(err, models.ErrNoRecord) {
+			http.NotFound(w, r)
+		} else {
+			app.serverError(w, r, err)
+		}
+		return
+	}
+
+	if !website.Guestbook.CanComment() {
+		app.clientError(w, http.StatusForbidden)
+		return
+	}
+
+	var form forms.CommentCreateForm
+	err = app.decodePostForm(r, &form)
+	if err != nil {
+		app.clientError(w, http.StatusBadRequest)
+		return
+	}
+
+	form.CheckField(validator.NotBlank(form.AuthorName), "authorName", "This field cannot be blank")
+	form.CheckField(validator.MaxChars(form.AuthorName, 256), "authorName", "This field cannot be more than 256 characters long")
+	form.CheckField(validator.MaxChars(form.AuthorEmail, 256), "authorEmail", "This field cannot be more than 256 characters long")
+	form.CheckField(validator.MaxChars(form.AuthorSite, 256), "authorSite", "This field cannot be more than 256 characters long")
+	form.CheckField(validator.NotBlank(form.Content), "content", "This field cannot be blank")
+	// TODO: Add optional field filled with window.location.href
+	// If it is populated, use as redirect URL
+	// Else redirect to homepage as stored in the website struct
+	redirectUrl := r.Header.Get("Referer")
+
+	if !form.Valid() {
+		views.GuestbookCommentCreateRemoteErrorView(redirectUrl, "Invalid Input").Render(r.Context(), w)
+		return
+	}
+
+	shortId := app.createShortId()
+	_, err = app.guestbookComments.Insert(shortId, website.Guestbook.ID, 0, form.AuthorName, form.AuthorEmail, form.AuthorSite, form.Content, "", true)
+	if err != nil {
+		app.serverError(w, r, err)
+		return
+	}
+	views.GuestbookCommentCreateRemoteSuccessView(redirectUrl).Render(r.Context(), w)
+}
+
 func (app *application) getCommentQueue(w http.ResponseWriter, r *http.Request) {
 	slug := r.PathValue("id")
 	website, err := app.websites.Get(slugToShortId(slug))
diff --git a/cmd/web/routes.go b/cmd/web/routes.go
index f6b6375..f9e768e 100644
--- a/cmd/web/routes.go
+++ b/cmd/web/routes.go
@@ -20,6 +20,7 @@ func (app *application) routes() http.Handler {
 	mux.Handle("/{$}", dynamic.ThenFunc(app.home))
 	mux.Handle("GET /websites/{id}/guestbook", dynamic.ThenFunc(app.getGuestbook))
 	mux.Handle("GET /websites/{id}/guestbook/comments", withCors.ThenFunc(app.getGuestbookCommentsSerialized))
+	mux.Handle("POST /websites/{id}/guestbook/comments/create/remote", standard.ThenFunc(app.postGuestbookCommentCreateRemote))
 	mux.Handle("GET /websites/{id}/guestbook/comments/create", dynamic.ThenFunc(app.getGuestbookCommentCreate))
 	mux.Handle("POST /websites/{id}/guestbook/comments/create", dynamic.ThenFunc(app.postGuestbookCommentCreate))
 	mux.Handle("GET /users/register", dynamic.ThenFunc(app.getUserRegister))
diff --git a/internal/models/guestbookcomment.go b/internal/models/guestbookcomment.go
index 4a7203b..d44de26 100644
--- a/internal/models/guestbookcomment.go
+++ b/internal/models/guestbookcomment.go
@@ -20,6 +20,12 @@ type GuestbookComment struct {
 	IsPublished bool
 }
 
+type GuestbookCommentSerialized struct {
+	AuthorName  string
+	CommentText string
+	Created     string
+}
+
 type GuestbookCommentModel struct {
 	DB *sql.DB
 }
@@ -28,6 +34,7 @@ type GuestbookCommentModelInterface interface {
 	Insert(shortId uint64, guestbookId, parentId int64, authorName, authorEmail, authorSite, commentText, pageUrl string, isPublished bool) (int64, error)
 	Get(shortId uint64) (GuestbookComment, error)
 	GetAll(guestbookId int64) ([]GuestbookComment, error)
+	GetAllSerialized(guestbookId int64) ([]GuestbookCommentSerialized, error)
 	GetDeleted(guestbookId int64) ([]GuestbookComment, error)
 	GetUnpublished(guestbookId int64) ([]GuestbookComment, error)
 	UpdateComment(comment *GuestbookComment) error
@@ -93,6 +100,30 @@ func (m *GuestbookCommentModel) GetAll(guestbookId int64) ([]GuestbookComment, e
 	return comments, nil
 }
 
+func (m *GuestbookCommentModel) GetAllSerialized(guestbookId int64) ([]GuestbookCommentSerialized, error) {
+	stmt := `SELECT AuthorName, CommentText, Created
+	    FROM guestbook_comments 
+	    WHERE GuestbookId = ? AND IsPublished = TRUE AND DELETED IS NULL
+	    ORDER BY Created DESC`
+	rows, err := m.DB.Query(stmt, guestbookId)
+	if err != nil {
+		return nil, err
+	}
+	var comments []GuestbookCommentSerialized
+	for rows.Next() {
+		var c GuestbookCommentSerialized
+		err = rows.Scan(&c.AuthorName, &c.CommentText, &c.Created)
+		if err != nil {
+			return nil, err
+		}
+		comments = append(comments, c)
+	}
+	if err = rows.Err(); err != nil {
+		return nil, err
+	}
+	return comments, nil
+}
+
 func (m *GuestbookCommentModel) GetDeleted(guestbookId int64) ([]GuestbookComment, error) {
 	stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite,
     CommentText, PageUrl, Created, IsPublished, Deleted
diff --git a/ui/views/common.templ b/ui/views/common.templ
index 13b5dd4..a9111f1 100644
--- a/ui/views/common.templ
+++ b/ui/views/common.templ
@@ -6,87 +6,85 @@ import "fmt"
 import "strings"
 
 type CommonData struct {
-    CurrentYear int
-    Flash string
-    IsAuthenticated bool
-    CSRFToken string
-    CurrentUser *models.User
-    IsHtmx bool
+	CurrentYear     int
+	Flash           string
+	IsAuthenticated bool
+	CSRFToken       string
+	CurrentUser     *models.User
+	IsHtmx          bool
 }
 
 func shortIdToSlug(shortId uint64) string {
-    return strconv.FormatUint(shortId, 36)
+	return strconv.FormatUint(shortId, 36)
 }
 
 func slugToShortId(slug string) uint64 {
-    id, _ := strconv.ParseUint(slug, 36, 64)
-    return id
+	id, _ := strconv.ParseUint(slug, 36, 64)
+	return id
 }
 
 func externalUrl(url string) string {
-    if !strings.HasPrefix(url, "http") {
-        return "http://" + url
-    }
-    return url
+	if !strings.HasPrefix(url, "http") {
+		return "http://" + url
+	}
+	return url
 }
 
 templ commonHeader() {
-    webweav.ing
-    webweav.ing
+	
{ c.CommentText } @@ -232,3 +238,35 @@ templ AllGuestbooksView(data CommonData, websites []models.Website) {
+ An error occurred while posting comment. { err }. Redirecting. +
++ Redirect +
+ + +} + +templ GuestbookCommentCreateRemoteSuccessView(url string) { + + + + + ++ Comment successfully posted. Redirecting. +
++ Redirect +
+ + +} diff --git a/ui/views/guestbooks_templ.go b/ui/views/guestbooks_templ.go index 3c55d7c..5b12c21 100644 --- a/ui/views/guestbooks_templ.go +++ b/ui/views/guestbooks_templ.go @@ -542,60 +542,89 @@ func GuestbookView(title string, data CommonData, website models.Website, guestb if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var27 string - templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorName) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 127, Col: 26} + if c.AuthorSite != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var28 string + templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 129, Col: 89} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + var templ_7745c5c3_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 131, Col: 24} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var32 string + templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(c.CommentText) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 136, Col: 24} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "