Compare commits

...

7 Commits

45 changed files with 2290 additions and 433 deletions

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/a-h/templ"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -17,6 +18,8 @@ import (
"git.grosinger.net/tgrosinger/saasitone/pkg/page" "git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/pkg/redirect" "git.grosinger.net/tgrosinger/saasitone/pkg/redirect"
"git.grosinger.net/tgrosinger/saasitone/pkg/services" "git.grosinger.net/tgrosinger/saasitone/pkg/services"
"git.grosinger.net/tgrosinger/saasitone/templ/layouts"
"git.grosinger.net/tgrosinger/saasitone/templ/pages"
"git.grosinger.net/tgrosinger/saasitone/templates" "git.grosinger.net/tgrosinger/saasitone/templates"
) )
@ -40,31 +43,6 @@ type (
db *services.DBClient db *services.DBClient
*services.TemplateRenderer *services.TemplateRenderer
} }
forgotPasswordForm struct {
Email string `form:"email" validate:"required,email"`
form.Submission
}
loginForm struct {
Email string `form:"email" validate:"required,email"`
Password string `form:"password" validate:"required"`
form.Submission
}
registerForm struct {
Name string `form:"name" validate:"required"`
Email string `form:"email" validate:"required,email"`
Password string `form:"password" validate:"required"`
ConfirmPassword string `form:"password-confirm" validate:"required,eqfield=Password"`
form.Submission
}
resetPasswordForm struct {
Password string `form:"password" validate:"required"`
ConfirmPassword string `form:"password-confirm" validate:"required,eqfield=Password"`
form.Submission
}
) )
func init() { func init() {
@ -101,16 +79,22 @@ func (h *Auth) Routes(g *echo.Group) {
func (h *Auth) ForgotPasswordPage(ctx echo.Context) error { func (h *Auth) ForgotPasswordPage(ctx echo.Context) error {
p := page.New(ctx) p := page.New(ctx)
p.Layout = templates.LayoutAuth
p.Name = templates.PageForgotPassword p.Name = templates.PageForgotPassword
p.Title = "Forgot password" p.Title = "Forgot password"
p.Form = form.Get[forgotPasswordForm](ctx)
return h.RenderPage(ctx, p) f := form.Get[pages.ForgotPasswordForm](ctx)
component := pages.ForgotPassword(p, f)
// TODO: This can be reused
p.LayoutComponent = func(content templ.Component) templ.Component {
return layouts.Auth(p, content)
}
return h.RenderPageTempl(ctx, p, component)
} }
func (h *Auth) ForgotPasswordSubmit(ctx echo.Context) error { func (h *Auth) ForgotPasswordSubmit(ctx echo.Context) error {
var input forgotPasswordForm var input pages.ForgotPasswordForm
succeed := func() error { succeed := func() error {
form.Clear(ctx) form.Clear(ctx)
@ -163,16 +147,22 @@ func (h *Auth) ForgotPasswordSubmit(ctx echo.Context) error {
func (h *Auth) LoginPage(ctx echo.Context) error { func (h *Auth) LoginPage(ctx echo.Context) error {
p := page.New(ctx) p := page.New(ctx)
p.Layout = templates.LayoutAuth
p.Name = templates.PageLogin p.Name = templates.PageLogin
p.Title = "Log in" p.Title = "Log in"
p.Form = form.Get[loginForm](ctx)
return h.RenderPage(ctx, p) f := form.Get[pages.LoginForm](ctx)
component := pages.Login(p, f)
// TODO: This can be reused
p.LayoutComponent = func(content templ.Component) templ.Component {
return layouts.Auth(p, content)
}
return h.RenderPageTempl(ctx, p, component)
} }
func (h *Auth) LoginSubmit(ctx echo.Context) error { func (h *Auth) LoginSubmit(ctx echo.Context) error {
var input loginForm var input pages.LoginForm
authFailed := func() error { authFailed := func() error {
input.SetFieldError("Email", "") input.SetFieldError("Email", "")
@ -231,16 +221,22 @@ func (h *Auth) Logout(ctx echo.Context) error {
func (h *Auth) RegisterPage(ctx echo.Context) error { func (h *Auth) RegisterPage(ctx echo.Context) error {
p := page.New(ctx) p := page.New(ctx)
p.Layout = templates.LayoutAuth
p.Name = templates.PageRegister p.Name = templates.PageRegister
p.Title = "Register" p.Title = "Register"
p.Form = form.Get[registerForm](ctx)
return h.RenderPage(ctx, p) f := form.Get[pages.RegisterForm](ctx)
component := pages.Register(p, f)
// TODO: This can be reused
p.LayoutComponent = func(content templ.Component) templ.Component {
return layouts.Auth(p, content)
}
return h.RenderPageTempl(ctx, p, component)
} }
func (h *Auth) RegisterSubmit(ctx echo.Context) error { func (h *Auth) RegisterSubmit(ctx echo.Context) error {
var input registerForm var input pages.RegisterForm
err := form.Submit(ctx, &input) err := form.Submit(ctx, &input)
@ -335,16 +331,22 @@ func (h *Auth) sendVerificationEmail(ctx echo.Context, usr sqlc.User) {
func (h *Auth) ResetPasswordPage(ctx echo.Context) error { func (h *Auth) ResetPasswordPage(ctx echo.Context) error {
p := page.New(ctx) p := page.New(ctx)
p.Layout = templates.LayoutAuth
p.Name = templates.PageResetPassword p.Name = templates.PageResetPassword
p.Title = "Reset password" p.Title = "Reset password"
p.Form = form.Get[resetPasswordForm](ctx)
return h.RenderPage(ctx, p) f := form.Get[pages.ResetPasswordForm](ctx)
component := pages.ResetPassword(p, f)
// TODO: This can be reused
p.LayoutComponent = func(content templ.Component) templ.Component {
return layouts.Auth(p, content)
}
return h.RenderPageTempl(ctx, p, component)
} }
func (h *Auth) ResetPasswordSubmit(ctx echo.Context) error { func (h *Auth) ResetPasswordSubmit(ctx echo.Context) error {
var input resetPasswordForm var input pages.ResetPasswordForm
err := form.Submit(ctx, &input) err := form.Submit(ctx, &input)

View File

@ -4,11 +4,14 @@ import (
"errors" "errors"
"time" "time"
"github.com/a-h/templ"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"git.grosinger.net/tgrosinger/saasitone/pkg/form" "git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page" "git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/pkg/services" "git.grosinger.net/tgrosinger/saasitone/pkg/services"
"git.grosinger.net/tgrosinger/saasitone/templ/layouts"
"git.grosinger.net/tgrosinger/saasitone/templ/pages"
"git.grosinger.net/tgrosinger/saasitone/templates" "git.grosinger.net/tgrosinger/saasitone/templates"
) )
@ -22,11 +25,6 @@ type (
cache *services.CacheClient cache *services.CacheClient
*services.TemplateRenderer *services.TemplateRenderer
} }
cacheForm struct {
Value string `form:"value"`
form.Submission
}
) )
func init() { func init() {
@ -46,10 +44,8 @@ func (h *Cache) Routes(g *echo.Group) {
func (h *Cache) Page(ctx echo.Context) error { func (h *Cache) Page(ctx echo.Context) error {
p := page.New(ctx) p := page.New(ctx)
p.Layout = templates.LayoutMain
p.Name = templates.PageCache p.Name = templates.PageCache
p.Title = "Set a cache entry" p.Title = "Set a cache entry"
p.Form = form.Get[cacheForm](ctx)
// Fetch the value from the cache // Fetch the value from the cache
value, err := h.cache. value, err := h.cache.
@ -57,20 +53,31 @@ func (h *Cache) Page(ctx echo.Context) error {
Key("page_cache_example"). Key("page_cache_example").
Fetch(ctx.Request().Context()) Fetch(ctx.Request().Context())
var valueStrPtr *string = nil
// Store the value in the page, so it can be rendered, if found // Store the value in the page, so it can be rendered, if found
switch { switch {
case err == nil: case err == nil:
p.Data = value.(string) valueStr := value.(string)
valueStrPtr = &valueStr
case errors.Is(err, services.ErrCacheMiss): case errors.Is(err, services.ErrCacheMiss):
default: default:
return fail(err, "failed to fetch from cache") return fail(err, "failed to fetch from cache")
} }
return h.RenderPage(ctx, p) f := form.Get[pages.CacheForm](ctx)
component := pages.Cache(p, f, valueStrPtr)
// TODO: This can be reused
p.LayoutComponent = func(content templ.Component) templ.Component {
return layouts.Main(p, content)
}
return h.RenderPageTempl(ctx, p, component)
} }
func (h *Cache) Submit(ctx echo.Context) error { func (h *Cache) Submit(ctx echo.Context) error {
var input cacheForm var input pages.CacheForm
if err := form.Submit(ctx, &input); err != nil { if err := form.Submit(ctx, &input); err != nil {
return err return err

View File

@ -3,12 +3,15 @@ package handlers
import ( import (
"fmt" "fmt"
"github.com/a-h/templ"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"git.grosinger.net/tgrosinger/saasitone/pkg/form" "git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page" "git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/pkg/services" "git.grosinger.net/tgrosinger/saasitone/pkg/services"
"git.grosinger.net/tgrosinger/saasitone/templ/layouts"
"git.grosinger.net/tgrosinger/saasitone/templ/pages"
"git.grosinger.net/tgrosinger/saasitone/templates" "git.grosinger.net/tgrosinger/saasitone/templates"
) )
@ -22,13 +25,6 @@ type (
mail *services.MailClient mail *services.MailClient
*services.TemplateRenderer *services.TemplateRenderer
} }
contactForm struct {
Email string `form:"email" validate:"required,email"`
Department string `form:"department" validate:"required,oneof=sales marketing hr"`
Message string `form:"message" validate:"required"`
form.Submission
}
) )
func init() { func init() {
@ -48,16 +44,22 @@ func (h *Contact) Routes(g *echo.Group) {
func (h *Contact) Page(ctx echo.Context) error { func (h *Contact) Page(ctx echo.Context) error {
p := page.New(ctx) p := page.New(ctx)
p.Layout = templates.LayoutMain
p.Name = templates.PageContact p.Name = templates.PageContact
p.Title = "Contact us" p.Title = "Contact us"
p.Form = form.Get[contactForm](ctx)
return h.RenderPage(ctx, p) f := form.Get[pages.ContactForm](ctx)
component := pages.Contact(p, f)
// TODO: This can be reused
p.LayoutComponent = func(content templ.Component) templ.Component {
return layouts.Main(p, content)
}
return h.RenderPageTempl(ctx, p, component)
} }
func (h *Contact) Submit(ctx echo.Context) error { func (h *Contact) Submit(ctx echo.Context) error {
var input contactForm var input pages.ContactForm
err := form.Submit(ctx, &input) err := form.Submit(ctx, &input)

View File

@ -3,12 +3,15 @@ package handlers
import ( import (
"net/http" "net/http"
"github.com/a-h/templ"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"git.grosinger.net/tgrosinger/saasitone/pkg/context" "git.grosinger.net/tgrosinger/saasitone/pkg/context"
"git.grosinger.net/tgrosinger/saasitone/pkg/log" "git.grosinger.net/tgrosinger/saasitone/pkg/log"
"git.grosinger.net/tgrosinger/saasitone/pkg/page" "git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/pkg/services" "git.grosinger.net/tgrosinger/saasitone/pkg/services"
"git.grosinger.net/tgrosinger/saasitone/templ/layouts"
"git.grosinger.net/tgrosinger/saasitone/templ/pages"
"git.grosinger.net/tgrosinger/saasitone/templates" "git.grosinger.net/tgrosinger/saasitone/templates"
) )
@ -38,13 +41,18 @@ func (e *Error) Page(err error, ctx echo.Context) {
// Render the error page // Render the error page
p := page.New(ctx) p := page.New(ctx)
p.Layout = templates.LayoutMain
p.Name = templates.PageError p.Name = templates.PageError
p.Title = http.StatusText(code) p.Title = http.StatusText(code)
p.StatusCode = code p.StatusCode = code
p.HTMX.Request.Enabled = false p.HTMX.Request.Enabled = false
if err = e.RenderPage(ctx, p); err != nil { component := pages.Error(p)
p.LayoutComponent = func(content templ.Component) templ.Component {
return layouts.Main(p, content)
}
if err = e.RenderPageTempl(ctx, p, component); err != nil {
log.Ctx(ctx).Error("failed to render error page", log.Ctx(ctx).Error("failed to render error page",
"error", err, "error", err,
) )

View File

@ -1,12 +1,12 @@
package handlers package handlers
import ( import (
"html/template" "github.com/a-h/templ"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"git.grosinger.net/tgrosinger/saasitone/pkg/page" "git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/pkg/services" "git.grosinger.net/tgrosinger/saasitone/pkg/services"
"git.grosinger.net/tgrosinger/saasitone/templ/layouts"
"git.grosinger.net/tgrosinger/saasitone/templ/pages" "git.grosinger.net/tgrosinger/saasitone/templ/pages"
"git.grosinger.net/tgrosinger/saasitone/templates" "git.grosinger.net/tgrosinger/saasitone/templates"
) )
@ -21,17 +21,6 @@ type (
*services.TemplateRenderer *services.TemplateRenderer
*services.DBClient *services.DBClient
} }
aboutData struct {
ShowCacheWarning bool
FrontendTabs []aboutTab
BackendTabs []aboutTab
}
aboutTab struct {
Title string
Body template.HTML
}
) )
func init() { func init() {
@ -51,7 +40,6 @@ func (h *Pages) Routes(g *echo.Group) {
func (h *Pages) Home(ctx echo.Context) error { func (h *Pages) Home(ctx echo.Context) error {
p := page.New(ctx) p := page.New(ctx)
p.Layout = templates.LayoutMain
p.Name = templates.PageHome p.Name = templates.PageHome
p.Metatags.Description = "Welcome to the homepage." p.Metatags.Description = "Welcome to the homepage."
p.Metatags.Keywords = []string{"Go", "MVC", "Web", "Software"} p.Metatags.Keywords = []string{"Go", "MVC", "Web", "Software"}
@ -60,12 +48,16 @@ func (h *Pages) Home(ctx echo.Context) error {
data := h.Post.FetchAll(&p.Pager) data := h.Post.FetchAll(&p.Pager)
component := pages.Home(p, data) component := pages.Home(p, data)
// TODO: This can be reused
p.LayoutComponent = func(content templ.Component) templ.Component {
return layouts.Main(p, content)
}
return h.RenderPageTempl(ctx, p, component) return h.RenderPageTempl(ctx, p, component)
} }
func (h *Pages) About(ctx echo.Context) error { func (h *Pages) About(ctx echo.Context) error {
p := page.New(ctx) p := page.New(ctx)
p.Layout = templates.LayoutMain
p.Name = templates.PageAbout p.Name = templates.PageAbout
p.Title = "About" p.Title = "About"
@ -75,33 +67,40 @@ func (h *Pages) About(ctx echo.Context) error {
// A simple example of how the Data field can contain anything you want to send to the templates // A simple example of how the Data field can contain anything you want to send to the templates
// even though you wouldn't normally send markup like this // even though you wouldn't normally send markup like this
p.Data = aboutData{ data := pages.AboutData{
ShowCacheWarning: true, ShowCacheWarning: true,
FrontendTabs: []aboutTab{ FrontendTabs: []pages.AboutTab{
{ {
Title: "HTMX", Title: "HTMX",
Body: template.HTML(`Completes HTML as a hypertext by providing attributes to AJAXify anything and much more. Visit <a href="https://htmx.org/">htmx.org</a> to learn more.`), Body: `Completes HTML as a hypertext by providing attributes to AJAXify anything and much more. Visit <a href="https://htmx.org/">htmx.org</a> to learn more.`,
}, },
{ {
Title: "Alpine.js", Title: "Alpine.js",
Body: template.HTML(`Drop-in, Vue-like functionality written directly in your markup. Visit <a href="https://alpinejs.dev/">alpinejs.dev</a> to learn more.`), Body: `Drop-in, Vue-like functionality written directly in your markup. Visit <a href="https://alpinejs.dev/">alpinejs.dev</a> to learn more.`,
}, },
{ {
Title: "Bulma", Title: "Bulma",
Body: template.HTML(`Ready-to-use frontend components that you can easily combine to build responsive web interfaces with no JavaScript requirements. Visit <a href="https://bulma.io/">bulma.io</a> to learn more.`), Body: `Ready-to-use frontend components that you can easily combine to build responsive web interfaces with no JavaScript requirements. Visit <a href="https://bulma.io/">bulma.io</a> to learn more.`,
}, },
}, },
BackendTabs: []aboutTab{ BackendTabs: []pages.AboutTab{
{ {
Title: "Echo", Title: "Echo",
Body: template.HTML(`High performance, extensible, minimalist Go web framework. Visit <a href="https://echo.labstack.com/">echo.labstack.com</a> to learn more.`), Body: `High performance, extensible, minimalist Go web framework. Visit <a href="https://echo.labstack.com/">echo.labstack.com</a> to learn more.`,
}, },
{ {
Title: "Ent", Title: "Ent",
Body: template.HTML(`Simple, yet powerful ORM for modeling and querying data. Visit <a href="https://entgo.io/">entgo.io</a> to learn more.`), Body: `Simple, yet powerful ORM for modeling and querying data. Visit <a href="https://entgo.io/">entgo.io</a> to learn more.`,
}, },
}, },
} }
return h.RenderPage(ctx, p) component := pages.About(p, data)
// TODO: This can be reused
p.LayoutComponent = func(content templ.Component) templ.Component {
return layouts.Main(p, content)
}
return h.RenderPageTempl(ctx, p, component)
} }

View File

@ -4,10 +4,13 @@ import (
"fmt" "fmt"
"math/rand" "math/rand"
"github.com/a-h/templ"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"git.grosinger.net/tgrosinger/saasitone/pkg/page" "git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/pkg/services" "git.grosinger.net/tgrosinger/saasitone/pkg/services"
"git.grosinger.net/tgrosinger/saasitone/templ/layouts"
"git.grosinger.net/tgrosinger/saasitone/templ/pages"
"git.grosinger.net/tgrosinger/saasitone/templates" "git.grosinger.net/tgrosinger/saasitone/templates"
) )
@ -17,11 +20,6 @@ type (
Search struct { Search struct {
*services.TemplateRenderer *services.TemplateRenderer
} }
searchResult struct {
Title string
URL string
}
) )
func init() { func init() {
@ -39,23 +37,28 @@ func (h *Search) Routes(g *echo.Group) {
func (h *Search) Page(ctx echo.Context) error { func (h *Search) Page(ctx echo.Context) error {
p := page.New(ctx) p := page.New(ctx)
p.Layout = templates.LayoutMain
p.Name = templates.PageSearch p.Name = templates.PageSearch
// Fake search results // Fake search results
var results []searchResult var results []pages.SearchResult
if search := ctx.QueryParam("query"); search != "" { if search := ctx.QueryParam("query"); search != "" {
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
title := "Lorem ipsum example ddolor sit amet" title := "Lorem ipsum example ddolor sit amet"
index := rand.Intn(len(title)) index := rand.Intn(len(title))
title = title[:index] + search + title[index:] title = title[:index] + search + title[index:]
results = append(results, searchResult{ results = append(results, pages.SearchResult{
Title: title, Title: title,
URL: fmt.Sprintf("https://www.%s.com", search), URL: fmt.Sprintf("https://www.%s.com", search),
}) })
} }
} }
p.Data = results
return h.RenderPage(ctx, p) component := pages.Search(p, results)
// TODO: This can be reused
p.LayoutComponent = func(content templ.Component) templ.Component {
return layouts.Main(p, content)
}
return h.RenderPageTempl(ctx, p, component)
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/a-h/templ"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -12,6 +13,8 @@ import (
"git.grosinger.net/tgrosinger/saasitone/pkg/page" "git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/pkg/services" "git.grosinger.net/tgrosinger/saasitone/pkg/services"
"git.grosinger.net/tgrosinger/saasitone/pkg/tasks" "git.grosinger.net/tgrosinger/saasitone/pkg/tasks"
"git.grosinger.net/tgrosinger/saasitone/templ/layouts"
"git.grosinger.net/tgrosinger/saasitone/templ/pages"
"git.grosinger.net/tgrosinger/saasitone/templates" "git.grosinger.net/tgrosinger/saasitone/templates"
) )
@ -25,12 +28,6 @@ type (
tasks *services.TaskClient tasks *services.TaskClient
*services.TemplateRenderer *services.TemplateRenderer
} }
taskForm struct {
Delay int `form:"delay" validate:"gte=0"`
Message string `form:"message" validate:"required"`
form.Submission
}
) )
func init() { func init() {
@ -50,16 +47,22 @@ func (h *Task) Routes(g *echo.Group) {
func (h *Task) Page(ctx echo.Context) error { func (h *Task) Page(ctx echo.Context) error {
p := page.New(ctx) p := page.New(ctx)
p.Layout = templates.LayoutMain
p.Name = templates.PageTask p.Name = templates.PageTask
p.Title = "Create a task" p.Title = "Create a task"
p.Form = form.Get[taskForm](ctx)
return h.RenderPage(ctx, p) f := form.Get[pages.TaskForm](ctx)
component := pages.Task(p, f)
// TODO: This can be reused
p.LayoutComponent = func(content templ.Component) templ.Component {
return layouts.Main(p, content)
}
return h.RenderPageTempl(ctx, p, component)
} }
func (h *Task) Submit(ctx echo.Context) error { func (h *Task) Submit(ctx echo.Context) error {
var input taskForm var input pages.TaskForm
err := form.Submit(ctx, &input) err := form.Submit(ctx, &input)

View File

@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/a-h/templ"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
echomw "github.com/labstack/echo/v4/middleware" echomw "github.com/labstack/echo/v4/middleware"
@ -56,6 +57,8 @@ type Page struct {
// The template extension should not be included in this value. // The template extension should not be included in this value.
Layout templates.Layout Layout templates.Layout
LayoutComponent func(content templ.Component) templ.Component
// Name stores the name of the page as well as the name of the template file which will be used to render // Name stores the name of the page as well as the name of the template file which will be used to render
// the content portion of the layout template. // the content portion of the layout template.
// This should match a template file located within the pages directory inside the templates directory. // This should match a template file located within the pages directory inside the templates directory.

View File

@ -16,7 +16,6 @@ import (
"git.grosinger.net/tgrosinger/saasitone/pkg/context" "git.grosinger.net/tgrosinger/saasitone/pkg/context"
"git.grosinger.net/tgrosinger/saasitone/pkg/log" "git.grosinger.net/tgrosinger/saasitone/pkg/log"
"git.grosinger.net/tgrosinger/saasitone/pkg/page" "git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/templ/layouts"
"git.grosinger.net/tgrosinger/saasitone/templates" "git.grosinger.net/tgrosinger/saasitone/templates"
) )
@ -116,7 +115,7 @@ func (t *TemplateRenderer) RenderPageTempl(ctx echo.Context, page page.Page, con
// Only partial content should be rendered. // Only partial content should be rendered.
err = content.Render(ctx.Request().Context(), &buf) err = content.Render(ctx.Request().Context(), &buf)
} else { } else {
err = layouts.Main(page, content).Render(ctx.Request().Context(), &buf) err = page.LayoutComponent(content).Render(ctx.Request().Context(), &buf)
} }
if err != nil { if err != nil {
return echo.NewHTTPError( return echo.NewHTTPError(

View File

@ -0,0 +1,11 @@
package components
templ CSRF(csrf string) {
<input type="hidden" name="csrf" value={ csrf }/>
}
templ FieldErrors(errors []string) {
for _, err := range errors {
<p class="help is-danger">{ err }</p>
}
}

View File

@ -0,0 +1,87 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package components
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
func CSRF(csrf string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"hidden\" name=\"csrf\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(csrf)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/components/forms.templ`, Line: 4, Col: 46}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func FieldErrors(errors []string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
for _, err := range errors {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p class=\"help is-danger\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(err)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/components/forms.templ`, Line: 9, Col: 33}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

42
templ/layouts/auth.templ Normal file
View File

@ -0,0 +1,42 @@
package layouts
import (
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/templ/components"
)
templ Auth(p page.Page, content templ.Component) {
<!DOCTYPE html>
<html lang="en">
<head>
@metatags(p)
@css()
@js()
</head>
<body>
<section class="hero is-info is-fullheight">
<div class="hero-body">
<div class="container">
<div class="columns is-centered">
<div class="column is-half">
if p.Title != "" {
<h1 class="title">{ p.Title }</h1>
}
<div class="box">
@components.Messages(p)
@content
<div class="content is-small has-text-centered" hx-boost="true">
<a href={ templ.URL(p.ToURL("login")) }>Login</a> &#9676;
<a href={ templ.URL(p.ToURL("register")) }>Create an account</a> &#9676;
<a href={ templ.URL(p.ToURL("forgot_password")) }>Forgot password?</a>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
@footer(p)
</body>
</html>
}

126
templ/layouts/auth_templ.go Normal file
View File

@ -0,0 +1,126 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package layouts
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/templ/components"
)
func Auth(p page.Page, content templ.Component) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\"><head>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = metatags(p).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = css().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = js().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</head><body><section class=\"hero is-info is-fullheight\"><div class=\"hero-body\"><div class=\"container\"><div class=\"columns is-centered\"><div class=\"column is-half\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if p.Title != "" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h1 class=\"title\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/layouts/auth.templ`, Line: 23, Col: 36}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"box\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.Messages(p).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = content.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"content is-small has-text-centered\" hx-boost=\"true\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL = templ.URL(p.ToURL("login"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Login</a> &#9676; <a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL = templ.URL(p.ToURL("register"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Create an account</a> &#9676; <a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 templ.SafeURL = templ.URL(p.ToURL("forgot_password"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Forgot password?</a></div></div></div></div></div></div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = footer(p).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

63
templ/pages/about.templ Normal file
View File

@ -0,0 +1,63 @@
package pages
import (
"strconv"
"fmt"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
)
type AboutData struct {
ShowCacheWarning bool
FrontendTabs []AboutTab
BackendTabs []AboutTab
}
type AboutTab struct {
Title string
Body string
}
templ About(p page.Page, data AboutData) {
if len(data.FrontendTabs) > 0 {
<p class="subtitle mt-5">Frontend</p>
<p class="mb-4">The following incredible projects make developing advanced, modern frontends possible and simple without having to write a single line of JS or CSS. You can go extremely far without leaving the comfort of Go with server-side rendered HTML.</p>
@tabs(data.FrontendTabs)
<div class="mb-4"></div>
}
if len(data.BackendTabs) > 0 {
<p class="subtitle mt-5">Backend</p>
<p class="mb-4">The following incredible projects provide the foundation of the Go backend. See the repository for a complete list of included projects.</p>
@tabs(data.BackendTabs)
<div class="mb-4"></div>
}
if (data.ShowCacheWarning) {
<article class="message is-warning mt-6">
<div class="message-header">
<p>Warning</p>
</div>
<div class="message-body">
This route has caching enabled so hot-reloading in the local environment will not work.
</div>
</article>
}
}
templ tabs(t []AboutTab) {
<div x-data="{tab: 0}">
<div class="tabs">
<ul>
for i, tab := range t {
<li :class={ fmt.Sprintf("{'is-active': tab === %d}", i) } @click={ "tab = " + strconv.Itoa(i) }><a>{ tab.Title }</a></li>
}
</ul>
</div>
for i, tab := range t {
<div x-show={ "tab === " + strconv.Itoa(i) }>
<p>
@templ.Raw(tab.Body)
</p>
</div>
}
</div>
}

187
templ/pages/about_templ.go Normal file
View File

@ -0,0 +1,187 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package pages
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"fmt"
"strconv"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
)
type AboutData struct {
ShowCacheWarning bool
FrontendTabs []AboutTab
BackendTabs []AboutTab
}
type AboutTab struct {
Title string
Body string
}
func About(p page.Page, data AboutData) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if len(data.FrontendTabs) > 0 {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p class=\"subtitle mt-5\">Frontend</p><p class=\"mb-4\">The following incredible projects make developing advanced, modern frontends possible and simple without having to write a single line of JS or CSS. You can go extremely far without leaving the comfort of Go with server-side rendered HTML.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = tabs(data.FrontendTabs).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <div class=\"mb-4\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.BackendTabs) > 0 {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p class=\"subtitle mt-5\">Backend</p><p class=\"mb-4\">The following incredible projects provide the foundation of the Go backend. See the repository for a complete list of included projects.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = tabs(data.BackendTabs).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <div class=\"mb-4\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.ShowCacheWarning {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<article class=\"message is-warning mt-6\"><div class=\"message-header\"><p>Warning</p></div><div class=\"message-body\">This route has caching enabled so hot-reloading in the local environment will not work.</div></article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func tabs(t []AboutTab) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div x-data=\"{tab: 0}\"><div class=\"tabs\"><ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for i, tab := range t {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li :class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("{'is-active': tab === %d}", i))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/about.templ`, Line: 51, Col: 61}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" @click=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs("tab = " + strconv.Itoa(i))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/about.templ`, Line: 51, Col: 99}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(tab.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/about.templ`, Line: 51, Col: 116}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for i, tab := range t {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div x-show=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("tab === " + strconv.Itoa(i))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/about.templ`, Line: 56, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(tab.Body).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

46
templ/pages/cache.templ Normal file
View File

@ -0,0 +1,46 @@
package pages
import (
"git.grosinger.net/tgrosinger/saasitone/templ/components"
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
)
type CacheForm struct {
Value string `form:"value"`
form.Submission
}
templ Cache(p page.Page, f *CacheForm, value *string) {
<form id="task" method="post" hx-post={ p.ToURL("cache.submit") }>
<article class="message">
<div class="message-header">
<p>Test the cache</p>
</div>
<div class="message-body">
This route handler shows how the default in-memory cache works. Try updating the value using the form below and see how it persists after you reload the page.
HTMX makes it easy to re-render the cached value after the form is submitted.
</div>
</article>
<label for="value" class="label">Value in cache: </label>
if value != nil {
<span class="tag is-success">{ *value }</span>
} else {
<i>(empty)</i>
}
<br/>
<br/>
<div class="field">
<label for="value" class="label">Value</label>
<div class="control">
<input id="value" name="value" class="input" value={ f.Value }/>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Update cache</button>
</div>
</div>
@components.CSRF(p.CSRF)
</form>
}

108
templ/pages/cache_templ.go Normal file
View File

@ -0,0 +1,108 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package pages
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/templ/components"
)
type CacheForm struct {
Value string `form:"value"`
form.Submission
}
func Cache(p page.Page, f *CacheForm, value *string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form id=\"task\" method=\"post\" hx-post=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(p.ToURL("cache.submit"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/cache.templ`, Line: 15, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><article class=\"message\"><div class=\"message-header\"><p>Test the cache</p></div><div class=\"message-body\">This route handler shows how the default in-memory cache works. Try updating the value using the form below and see how it persists after you reload the page. HTMX makes it easy to re-render the cached value after the form is submitted.</div></article><label for=\"value\" class=\"label\">Value in cache: </label> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if value != nil {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span class=\"tag is-success\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(*value)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/cache.templ`, Line: 27, Col: 40}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<i>(empty)</i>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<br><br><div class=\"field\"><label for=\"value\" class=\"label\">Value</label><div class=\"control\"><input id=\"value\" name=\"value\" class=\"input\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(f.Value)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/cache.templ`, Line: 36, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></div></div><div class=\"field is-grouped\"><div class=\"control\"><button class=\"button is-link\">Update cache</button></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.CSRF(p.CSRF).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

74
templ/pages/contact.templ Normal file
View File

@ -0,0 +1,74 @@
package pages
import (
"git.grosinger.net/tgrosinger/saasitone/templ/components"
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
)
type ContactForm struct {
Email string `form:"email" validate:"required,email"`
Department string `form:"department" validate:"required,oneof=sales marketing hr"`
Message string `form:"message" validate:"required"`
form.Submission
}
templ Contact(p page.Page, f *ContactForm) {
if p.HTMX.Request.Target != "contact" {
<article class="message is-link">
<div class="message-body">
<p>This is an example of a form with inline, server-side validation and HTMX-powered AJAX submissions without writing a single line of JavaScript.</p>
<p>Only the form below will update async upon submission.</p>
</div>
</article>
}
if f.IsDone() {
<article class="message is-large is-success">
<div class="message-header">
<p>Thank you!</p>
</div>
<div class="message-body">
No email was actually sent but this entire operation was handled server-side and degrades without JavaScript enabled.
</div>
</article>
} else {
<form id="contact" method="post" hx-post={ p.ToURL("contact.submit") }>
<div class="field">
<label for="email" class="label">Email address</label>
<div class="control">
<input id="email" name="email" type="email" class={ "input", f.Submission.GetFieldStatusClass("Email") } value={ f.Email }/>
</div>
@components.FieldErrors(f.Submission.GetFieldErrors("Email"))
</div>
<div class="control">
<label class="label">Department</label>
<label class="radio">
<input type="radio" name="department" value="sales" checked?={ f.Department == "sales" }/>
Sales
</label>
<label class="radio">
<input type="radio" name="department" value="marketing" checked?={ f.Department == "marketing" }/>
Marketing
</label>
<label class="radio">
<input type="radio" name="department" value="hr" checked?={ f.Department == "hr" }/>
HR
</label>
@components.FieldErrors(f.Submission.GetFieldErrors("Department"))
</div>
<div class="field">
<label for="message" class="label">Message</label>
<div class="control">
<textarea id="message" name="message" class={ "textarea", f.Submission.GetFieldStatusClass("Message") }>{ f.Message }</textarea>
</div>
@components.FieldErrors(f.Submission.GetFieldErrors("Message"))
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Submit</button>
</div>
</div>
@components.CSRF(p.CSRF)
</form>
}
}

View File

@ -0,0 +1,206 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package pages
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/templ/components"
)
type ContactForm struct {
Email string `form:"email" validate:"required,email"`
Department string `form:"department" validate:"required,oneof=sales marketing hr"`
Message string `form:"message" validate:"required"`
form.Submission
}
func Contact(p page.Page, f *ContactForm) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if p.HTMX.Request.Target != "contact" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<article class=\"message is-link\"><div class=\"message-body\"><p>This is an example of a form with inline, server-side validation and HTMX-powered AJAX submissions without writing a single line of JavaScript.</p><p>Only the form below will update async upon submission.</p></div></article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if f.IsDone() {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<article class=\"message is-large is-success\"><div class=\"message-header\"><p>Thank you!</p></div><div class=\"message-body\">No email was actually sent but this entire operation was handled server-side and degrades without JavaScript enabled.</div></article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form id=\"contact\" method=\"post\" hx-post=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(p.ToURL("contact.submit"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/contact.templ`, Line: 35, Col: 70}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><div class=\"field\"><label for=\"email\" class=\"label\">Email address</label><div class=\"control\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 = []any{"input", f.Submission.GetFieldStatusClass("Email")}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input id=\"email\" name=\"email\" type=\"email\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var3).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/contact.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(f.Email)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/contact.templ`, Line: 39, Col: 125}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("Email")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"control\"><label class=\"label\">Department</label> <label class=\"radio\"><input type=\"radio\" name=\"department\" value=\"sales\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if f.Department == "sales" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" checked")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("> Sales</label> <label class=\"radio\"><input type=\"radio\" name=\"department\" value=\"marketing\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if f.Department == "marketing" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" checked")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("> Marketing</label> <label class=\"radio\"><input type=\"radio\" name=\"department\" value=\"hr\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if f.Department == "hr" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" checked")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("> HR</label>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("Department")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"field\"><label for=\"message\" class=\"label\">Message</label><div class=\"control\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 = []any{"textarea", f.Submission.GetFieldStatusClass("Message")}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<textarea id=\"message\" name=\"message\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var6).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/contact.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(f.Message)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/contact.templ`, Line: 62, Col: 120}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</textarea></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("Message")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"field is-grouped\"><div class=\"control\"><button class=\"button is-link\">Submit</button></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.CSRF(p.CSRF).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

19
templ/pages/error.templ Normal file
View File

@ -0,0 +1,19 @@
package pages
import (
"net/http"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
)
templ Error(p page.Page) {
if p.StatusCode >= 500 {
<p>Please try again.</p>
} else if p.StatusCode == http.StatusUnauthorized || p.StatusCode == http.StatusForbidden {
<p>You are not authorized to view the requested page.</p>
} else if p.StatusCode == http.StatusNotFound {
<p>Click <a href={ templ.URL(p.ToURL("home")) }>here</a> to return home</p>
} else {
<p>Something went wrong</p>
}
}

View File

@ -0,0 +1,67 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package pages
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"net/http"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
)
func Error(p page.Page) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if p.StatusCode >= 500 {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>Please try again.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if p.StatusCode == http.StatusUnauthorized || p.StatusCode == http.StatusForbidden {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>You are not authorized to view the requested page.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if p.StatusCode == http.StatusNotFound {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>Click <a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(p.ToURL("home"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">here</a> to return home</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>Something went wrong</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -0,0 +1,36 @@
package pages
import (
"git.grosinger.net/tgrosinger/saasitone/templ/components"
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
)
type ForgotPasswordForm struct {
Email string `form:"email" validate:"required,email"`
form.Submission
}
templ ForgotPassword(p page.Page, f *ForgotPasswordForm) {
<form method="post" hx-boost="true" action={ templ.URL(p.ToURL("forgot_password.submit")) }>
<div class="content">
<p>Enter your email address and we'll email you a link that allows you to reset your password.</p>
</div>
<div class="field">
<label for="email" class="label">Email address</label>
<div class="control">
<input id="email" type="email" name="email" class={ "input", f.Submission.GetFieldStatusClass("Email") } value={ f.Email }/>
@components.FieldErrors(f.Submission.GetFieldErrors("Email"))
</div>
</div>
<div class="field is-grouped">
<p class="control">
<button class="button is-primary">Reset password</button>
</p>
<p class="control">
<a href={ templ.URL(p.ToURL("home")) } class="button is-light">Cancel</a>
</p>
</div>
@components.CSRF(p.CSRF)
</form>
}

View File

@ -0,0 +1,115 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package pages
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/templ/components"
)
type ForgotPasswordForm struct {
Email string `form:"email" validate:"required,email"`
form.Submission
}
func ForgotPassword(p page.Page, f *ForgotPasswordForm) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form method=\"post\" hx-boost=\"true\" action=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(p.ToURL("forgot_password.submit"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><div class=\"content\"><p>Enter your email address and we'll email you a link that allows you to reset your password.</p></div><div class=\"field\"><label for=\"email\" class=\"label\">Email address</label><div class=\"control\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 = []any{"input", f.Submission.GetFieldStatusClass("Email")}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input id=\"email\" type=\"email\" name=\"email\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var3).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/forgot-password.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(f.Email)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/forgot-password.templ`, Line: 22, Col: 124}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("Email")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"field is-grouped\"><p class=\"control\"><button class=\"button is-primary\">Reset password</button></p><p class=\"control\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 templ.SafeURL = templ.URL(p.ToURL("home"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"button is-light\">Cancel</a></p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.CSRF(p.CSRF).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -133,7 +133,7 @@ func Home(p page.Page, posts []services.Post) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if p.HTMX.Request.Target != "posts" { if p.HTMX.Request.Target != "posts" {
templ_7745c5c3_Err = fileMsg(p).Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = fileMsg().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -208,7 +208,7 @@ func topContent(p page.Page) templ.Component {
}) })
} }
func fileMsg(p page.Page) templ.Component { func fileMsg() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer { if !templ_7745c5c3_IsBuffer {

42
templ/pages/login.templ Normal file
View File

@ -0,0 +1,42 @@
package pages
import (
"git.grosinger.net/tgrosinger/saasitone/templ/components"
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
)
type LoginForm struct {
Email string `form:"email" validate:"required,email"`
Password string `form:"password" validate:"required"`
form.Submission
}
templ Login(p page.Page, f *LoginForm) {
<form method="post" hx-boost="true" action={ templ.URL(p.ToURL("login.submit")) }>
@components.Messages(p)
<div class="field">
<label for="email" class="label">Email address</label>
<div class="control">
<input id="email" type="email" name="email" class={ "input", f.Submission.GetFieldStatusClass("Email") } value={ f.Email }/>
@components.FieldErrors(f.Submission.GetFieldErrors("Email"))
</div>
</div>
<div class="field">
<label for="password" class="label">Password</label>
<div class="control">
<input id="password" type="password" name="password" placeholder="*******" class={ "input", f.Submission.GetFieldStatusClass("Password") }/>
@components.FieldErrors(f.Submission.GetFieldErrors("Password"))
</div>
</div>
<div class="field is-grouped">
<p class="control">
<button class="button is-primary">Log in</button>
</p>
<p class="control">
<a href={ templ.URL(p.ToURL("home")) } class="button is-light">Cancel</a>
</p>
</div>
@components.CSRF(p.CSRF)
</form>
}

154
templ/pages/login_templ.go Normal file
View File

@ -0,0 +1,154 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package pages
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/templ/components"
)
type LoginForm struct {
Email string `form:"email" validate:"required,email"`
Password string `form:"password" validate:"required"`
form.Submission
}
func Login(p page.Page, f *LoginForm) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form method=\"post\" hx-boost=\"true\" action=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(p.ToURL("login.submit"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.Messages(p).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"field\"><label for=\"email\" class=\"label\">Email address</label><div class=\"control\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 = []any{"input", f.Submission.GetFieldStatusClass("Email")}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input id=\"email\" type=\"email\" name=\"email\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var3).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/login.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(f.Email)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/login.templ`, Line: 21, Col: 124}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("Email")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"field\"><label for=\"password\" class=\"label\">Password</label><div class=\"control\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 = []any{"input", f.Submission.GetFieldStatusClass("Password")}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input id=\"password\" type=\"password\" name=\"password\" placeholder=\"*******\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var6).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/login.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("Password")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"field is-grouped\"><p class=\"control\"><button class=\"button is-primary\">Log in</button></p><p class=\"control\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 templ.SafeURL = templ.URL(p.ToURL("home"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var8)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"button is-light\">Cancel</a></p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.CSRF(p.CSRF).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -0,0 +1,57 @@
package pages
import (
"git.grosinger.net/tgrosinger/saasitone/templ/components"
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
)
type RegisterForm struct {
Name string `form:"name" validate:"required"`
Email string `form:"email" validate:"required,email"`
Password string `form:"password" validate:"required"`
ConfirmPassword string `form:"password-confirm" validate:"required,eqfield=Password"`
form.Submission
}
templ Register(p page.Page, f *RegisterForm) {
<form method="post" hx-boost="true" action={ templ.URL(p.ToURL("register.submit")) }>
<div class="field">
<label for="name" class="label">Name</label>
<div class="control">
<input type="text" id="name" name="name" class={ "input", f.Submission.GetFieldStatusClass("Name") } value={ f.Name }/>
@components.FieldErrors(f.Submission.GetFieldErrors("Name"))
</div>
</div>
<div class="field">
<label for="email" class="label">Email address</label>
<div class="control">
<input type="email" id="email" name="email" class={ "input", f.Submission.GetFieldStatusClass("Email") } value={ f.Email }/>
@components.FieldErrors(f.Submission.GetFieldErrors("Email"))
</div>
</div>
<div class="field">
<label for="password" class="label">Password</label>
<div class="control">
<input type="password" id="password" name="password" placeholder="*******" class={ "input", f.Submission.GetFieldStatusClass("Password") }/>
@components.FieldErrors(f.Submission.GetFieldErrors("Password"))
</div>
</div>
<div class="field">
<label for="password-confirm" class="label">Confirm password</label>
<div class="control">
<input type="password" id="password-confirm" name="password-confirm" placeholder="*******" class={ "input", f.Submission.GetFieldStatusClass("ConfigmPassword") }/>
@components.FieldErrors(f.Submission.GetFieldErrors("ConfigmPassword"))
</div>
</div>
<div class="field is-grouped">
<p class="control">
<button class="button is-primary">Register</button>
</p>
<p class="control">
<a href={ templ.URL(p.ToURL("home")) } class="button is-light">Cancel</a>
</p>
</div>
@components.CSRF(p.CSRF)
</form>
}

View File

@ -0,0 +1,221 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package pages
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/templ/components"
)
type RegisterForm struct {
Name string `form:"name" validate:"required"`
Email string `form:"email" validate:"required,email"`
Password string `form:"password" validate:"required"`
ConfirmPassword string `form:"password-confirm" validate:"required,eqfield=Password"`
form.Submission
}
func Register(p page.Page, f *RegisterForm) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form method=\"post\" hx-boost=\"true\" action=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(p.ToURL("register.submit"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><div class=\"field\"><label for=\"name\" class=\"label\">Name</label><div class=\"control\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 = []any{"input", f.Submission.GetFieldStatusClass("Name")}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"text\" id=\"name\" name=\"name\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var3).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/register.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(f.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/register.templ`, Line: 22, Col: 119}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("Name")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"field\"><label for=\"email\" class=\"label\">Email address</label><div class=\"control\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 = []any{"input", f.Submission.GetFieldStatusClass("Email")}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"email\" id=\"email\" name=\"email\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var6).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/register.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(f.Email)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/register.templ`, Line: 29, Col: 124}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("Email")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"field\"><label for=\"password\" class=\"label\">Password</label><div class=\"control\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 = []any{"input", f.Submission.GetFieldStatusClass("Password")}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"password\" id=\"password\" name=\"password\" placeholder=\"*******\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var9).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/register.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("Password")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"field\"><label for=\"password-confirm\" class=\"label\">Confirm password</label><div class=\"control\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 = []any{"input", f.Submission.GetFieldStatusClass("ConfigmPassword")}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var11...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"password\" id=\"password-confirm\" name=\"password-confirm\" placeholder=\"*******\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var11).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/register.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("ConfigmPassword")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"field is-grouped\"><p class=\"control\"><button class=\"button is-primary\">Register</button></p><p class=\"control\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 templ.SafeURL = templ.URL(p.ToURL("home"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var13)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"button is-light\">Cancel</a></p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.CSRF(p.CSRF).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -0,0 +1,38 @@
package pages
import (
"git.grosinger.net/tgrosinger/saasitone/templ/components"
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
)
type ResetPasswordForm struct {
Password string `form:"password" validate:"required"`
ConfirmPassword string `form:"password-confirm" validate:"required,eqfield=Password"`
form.Submission
}
templ ResetPassword(p page.Page, f *ResetPasswordForm) {
<form method="post" hx-boost="true" action={ templ.URL(p.Path) }>
<div class="field">
<label for="password" class="label">Password</label>
<div class="control">
<input type="password" id="password" name="password" placeholder="*******" class={ "input", f.Submission.GetFieldStatusClass("Password") }/>
@components.FieldErrors(f.Submission.GetFieldErrors("Password"))
</div>
</div>
<div class="field">
<label for="password-confirm" class="label">Confirm password</label>
<div class="control">
<input type="password" id="password-confirm" name="password-confirm" placeholder="*******" class={ "input", f.Submission.GetFieldStatusClass("ConfirmPassword") }/>
@components.FieldErrors(f.Submission.GetFieldErrors("ConfigmPassword"))
</div>
</div>
<div class="field is-grouped">
<p class="control">
<button class="button is-primary">Update password</button>
</p>
</div>
@components.CSRF(p.CSRF)
</form>
}

View File

@ -0,0 +1,124 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package pages
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/templ/components"
)
type ResetPasswordForm struct {
Password string `form:"password" validate:"required"`
ConfirmPassword string `form:"password-confirm" validate:"required,eqfield=Password"`
form.Submission
}
func ResetPassword(p page.Page, f *ResetPasswordForm) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form method=\"post\" hx-boost=\"true\" action=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(p.Path)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><div class=\"field\"><label for=\"password\" class=\"label\">Password</label><div class=\"control\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 = []any{"input", f.Submission.GetFieldStatusClass("Password")}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"password\" id=\"password\" name=\"password\" placeholder=\"*******\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var3).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/reset-password.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("Password")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"field\"><label for=\"password-confirm\" class=\"label\">Confirm password</label><div class=\"control\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 = []any{"input", f.Submission.GetFieldStatusClass("ConfirmPassword")}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"password\" id=\"password-confirm\" name=\"password-confirm\" placeholder=\"*******\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var5).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/reset-password.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("ConfigmPassword")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"field is-grouped\"><p class=\"control\"><button class=\"button is-primary\">Update password</button></p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.CSRF(p.CSRF).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

14
templ/pages/search.templ Normal file
View File

@ -0,0 +1,14 @@
package pages
import "git.grosinger.net/tgrosinger/saasitone/pkg/page"
type SearchResult struct {
Title string
URL string
}
templ Search(p page.Page, results []SearchResult) {
for _, result := range results {
<a class="panel-block" href={ templ.URL(result.URL) }>{ result.Title }</a>
}
}

View File

@ -0,0 +1,66 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package pages
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import "git.grosinger.net/tgrosinger/saasitone/pkg/page"
type SearchResult struct {
Title string
URL string
}
func Search(p page.Page, results []SearchResult) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
for _, result := range results {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"panel-block\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(result.URL)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(result.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/search.templ`, Line: 12, Col: 70}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

55
templ/pages/task.templ Normal file
View File

@ -0,0 +1,55 @@
package pages
import (
"strconv"
"git.grosinger.net/tgrosinger/saasitone/templ/components"
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
)
type TaskForm struct {
Delay int `form:"delay" validate:"gte=0"`
Message string `form:"message" validate:"required"`
form.Submission
}
templ Task(p page.Page, f *TaskForm) {
if p.HTMX.Request.Target != "task" {
<article class="message is-link">
<div class="message-body">
<p>Submitting this form will create an <i>ExampleTask</i> in the task queue. After the specified delay, the message will be logged by the queue processor.</p>
<p>See pkg/tasks and the README for more information.</p>
</div>
</article>
}
@taskForm(p, f)
}
templ taskForm(p page.Page, f *TaskForm) {
<form id="task" method="post" hx-post={ p.ToURL("task.submit") }>
@components.Messages(p)
<div class="field">
<label for="delay" class="label">Delay (in seconds)</label>
<div class="control">
<input type="number" id="delay" name="delay" class={ "input", f.Submission.GetFieldStatusClass("Delay") } value={ strconv.Itoa(f.Delay) }/>
</div>
<p class="help">How long to wait until the task is executed</p>
@components.FieldErrors(f.Submission.GetFieldErrors("Delay"))
</div>
<div class="field">
<label for="message" class="label">Message</label>
<div class="control">
<textarea id="message" name="message" class={ "textarea", f.Submission.GetFieldStatusClass("Message") }>{ f.Message }</textarea>
</div>
<p class="help">The message the task will output to the log</p>
@components.FieldErrors(f.Submission.GetFieldErrors("Message"))
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Add task to queue</button>
</div>
</div>
@components.CSRF(p.CSRF)
</form>
}

194
templ/pages/task_templ.go Normal file
View File

@ -0,0 +1,194 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package pages
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"strconv"
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/templ/components"
)
type TaskForm struct {
Delay int `form:"delay" validate:"gte=0"`
Message string `form:"message" validate:"required"`
form.Submission
}
func Task(p page.Page, f *TaskForm) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if p.HTMX.Request.Target != "task" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<article class=\"message is-link\"><div class=\"message-body\"><p>Submitting this form will create an <i>ExampleTask</i> in the task queue. After the specified delay, the message will be logged by the queue processor.</p><p>See pkg/tasks and the README for more information.</p></div></article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = taskForm(p, f).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func taskForm(p page.Page, f *TaskForm) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form id=\"task\" method=\"post\" hx-post=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(p.ToURL("task.submit"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/task.templ`, Line: 30, Col: 63}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.Messages(p).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"field\"><label for=\"delay\" class=\"label\">Delay (in seconds)</label><div class=\"control\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 = []any{"input", f.Submission.GetFieldStatusClass("Delay")}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"number\" id=\"delay\" name=\"delay\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var4).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/task.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(f.Delay))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/task.templ`, Line: 35, Col: 139}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></div><p class=\"help\">How long to wait until the task is executed</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("Delay")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"field\"><label for=\"message\" class=\"label\">Message</label><div class=\"control\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 = []any{"textarea", f.Submission.GetFieldStatusClass("Message")}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var7...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<textarea id=\"message\" name=\"message\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var7).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/task.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(f.Message)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/task.templ`, Line: 43, Col: 119}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</textarea></div><p class=\"help\">The message the task will output to the log</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("Message")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"field is-grouped\"><div class=\"control\"><button class=\"button is-link\">Add task to queue</button></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.CSRF(p.CSRF).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -1,41 +0,0 @@
{{define "content"}}
{{- if .Data.FrontendTabs}}
<p class="subtitle mt-5">Frontend</p>
<p class="mb-4">The following incredible projects make developing advanced, modern frontends possible and simple without having to write a single line of JS or CSS. You can go extremely far without leaving the comfort of Go with server-side rendered HTML.</p>
{{template "tabs" .Data.FrontendTabs}}
<div class="mb-4"></div>
{{- end}}
{{- if .Data.BackendTabs}}
<p class="subtitle mt-5">Backend</p>
<p class="mb-4">The following incredible projects provide the foundation of the Go backend. See the repository for a complete list of included projects.</p>
{{template "tabs" .Data.BackendTabs}}
<div class="mb-4"></div>
{{end}}
{{- if .Data.ShowCacheWarning}}
<article class="message is-warning mt-6">
<div class="message-header">
<p>Warning</p>
</div>
<div class="message-body">
This route has caching enabled so hot-reloading in the local environment will not work.
</div>
</article>
{{- end}}
{{end}}
{{define "tabs"}}
<div x-data="{tab: 0}">
<div class="tabs">
<ul>
{{- range $index, $tab := .}}
<li :class="{'is-active': tab === {{$index}}}" @click="tab = {{$index}}"><a>{{.Title}}</a></li>
{{- end}}
</ul>
</div>
{{- range $index, $tab := .}}
<div x-show="tab == {{$index}}"><p> &rarr; {{.Body}}</p></div>
{{- end}}
</div>
{{end}}

View File

@ -1,36 +0,0 @@
{{define "content"}}
<form id="task" method="post" hx-post="{{url "cache.submit"}}">
<article class="message">
<div class="message-header">
<p>Test the cache</p>
</div>
<div class="message-body">
This route handler shows how the default in-memory cache works. Try updating the value using the form below and see how it persists after you reload the page.
HTMX makes it easy to re-render the cached value after the form is submitted.
</div>
</article>
<label for="value" class="label">Value in cache: </label>
{{if .Data}}
<span class="tag is-success">{{.Data}}</span>
{{- else}}
<i>(empty)</i>
{{- end}}
<br/><br/>
<div class="field">
<label for="value" class="label">Value</label>
<div class="control">
<input id="value" name="value" class="input" value="{{.Form.Value}}"/>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Update cache</button>
</div>
</div>
{{template "csrf" .}}
</form>
{{end}}

View File

@ -1,68 +0,0 @@
{{define "content"}}
{{- if not (eq .HTMX.Request.Target "contact")}}
<article class="message is-link">
<div class="message-body">
<p>This is an example of a form with inline, server-side validation and HTMX-powered AJAX submissions without writing a single line of JavaScript.</p>
<p>Only the form below will update async upon submission.</p>
</div>
</article>
{{- end}}
{{template "form" .}}
{{end}}
{{define "form"}}
{{- if .Form.IsDone}}
<article class="message is-large is-success">
<div class="message-header">
<p>Thank you!</p>
</div>
<div class="message-body">
No email was actually sent but this entire operation was handled server-side and degrades without JavaScript enabled.
</div>
</article>
{{- else}}
<form id="contact" method="post" hx-post="{{url "contact.submit"}}">
<div class="field">
<label for="email" class="label">Email address</label>
<div class="control">
<input id="email" name="email" type="email" class="input {{.Form.GetFieldStatusClass "Email"}}" value="{{.Form.Email}}">
</div>
{{template "field-errors" (.Form.GetFieldErrors "Email")}}
</div>
<div class="control">
<label class="label">Department</label>
<label class="radio">
<input type="radio" name="department" value="sales" {{if eq .Form.Department "sales"}}checked{{end}}/>
Sales
</label>
<label class="radio">
<input type="radio" name="department" value="marketing" {{if eq .Form.Department "marketing"}}checked{{end}}/>
Marketing
</label>
<label class="radio">
<input type="radio" name="department" value="hr" {{if eq .Form.Department "hr"}}checked{{end}}/>
HR
</label>
{{template "field-errors" (.Form.GetFieldErrors "Department")}}
</div>
<div class="field">
<label for="message" class="label">Message</label>
<div class="control">
<textarea id="message" name="message" class="textarea {{.Form.GetFieldStatusClass "Message"}}">{{.Form.Message}}</textarea>
</div>
{{template "field-errors" (.Form.GetFieldErrors "Message")}}
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Submit</button>
</div>
</div>
{{template "csrf" .}}
</form>
{{- end}}
{{end}}

View File

@ -1,11 +0,0 @@
{{define "content"}}
{{if ge .StatusCode 500}}
<p>Please try again.</p>
{{else if or (eq .StatusCode 403) (eq .StatusCode 401)}}
<p>You are not authorized to view the requested page.</p>
{{else if eq .StatusCode 404}}
<p>Click {{link (url "home") "here" .Path}} to return home</p>
{{else}}
<p>Something went wrong</p>
{{end}}
{{end}}

View File

@ -1,23 +0,0 @@
{{define "content"}}
<form method="post" hx-boost="true" action="{{url "forgot_password.submit"}}">
<div class="content">
<p>Enter your email address and we'll email you a link that allows you to reset your password.</p>
</div>
<div class="field">
<label for="email" class="label">Email address</label>
<div class="control">
<input id="email" type="email" name="email" class="input {{.Form.Submission.GetFieldStatusClass "Email"}}" value="{{.Form.Email}}">
{{template "field-errors" (.Form.Submission.GetFieldErrors "Email")}}
</div>
</div>
<div class="field is-grouped">
<p class="control">
<button class="button is-primary">Reset password</button>
</p>
<p class="control">
<a href="{{url "home"}}" class="button is-light">Cancel</a>
</p>
</div>
{{template "csrf" .}}
</form>
{{end}}

View File

@ -1,28 +0,0 @@
{{define "content"}}
<form method="post" hx-boost="true" action="{{url "login.submit"}}">
{{template "messages" .}}
<div class="field">
<label for="email" class="label">Email address</label>
<div class="control">
<input id="email" type="email" name="email" class="input {{.Form.Submission.GetFieldStatusClass "Email"}}" value="{{.Form.Email}}">
{{template "field-errors" (.Form.Submission.GetFieldErrors "Email")}}
</div>
</div>
<div class="field">
<label for="password" class="label">Password</label>
<div class="control">
<input id="password" type="password" name="password" placeholder="*******" class="input {{.Form.Submission.GetFieldStatusClass "Password"}}">
{{template "field-errors" (.Form.Submission.GetFieldErrors "Password")}}
</div>
</div>
<div class="field is-grouped">
<p class="control">
<button class="button is-primary">Log in</button>
</p>
<p class="control">
<a href="{{url "home"}}" class="button is-light">Cancel</a>
</p>
</div>
{{template "csrf" .}}
</form>
{{end}}

View File

@ -1,41 +0,0 @@
{{define "content"}}
<form method="post" hx-boost="true" action="{{url "register.submit"}}">
<div class="field">
<label for="name" class="label">Name</label>
<div class="control">
<input type="text" id="name" name="name" class="input {{.Form.GetFieldStatusClass "Name"}}" value="{{.Form.Name}}">
{{template "field-errors" (.Form.GetFieldErrors "Name")}}
</div>
</div>
<div class="field">
<label for="email" class="label">Email address</label>
<div class="control">
<input type="email" id="email" name="email" class="input {{.Form.GetFieldStatusClass "Email"}}" value="{{.Form.Email}}">
{{template "field-errors" (.Form.GetFieldErrors "Email")}}
</div>
</div>
<div class="field">
<label for="password" class="label">Password</label>
<div class="control">
<input type="password" id="password" name="password" placeholder="*******" class="input {{.Form.GetFieldStatusClass "Password"}}">
{{template "field-errors" (.Form.GetFieldErrors "Password")}}
</div>
</div>
<div class="field">
<label for="password-confirm" class="label">Confirm password</label>
<div class="control">
<input type="password" id="password-confirm" name="password-confirm" placeholder="*******" class="input {{.Form.GetFieldStatusClass "ConfirmPassword"}}">
{{template "field-errors" (.Form.GetFieldErrors "ConfirmPassword")}}
</div>
</div>
<div class="field is-grouped">
<p class="control">
<button class="button is-primary">Register</button>
</p>
<p class="control">
<a href="{{url "home"}}" class="button is-light">Cancel</a>
</p>
</div>
{{template "csrf" .}}
</form>
{{end}}

View File

@ -1,24 +0,0 @@
{{define "content"}}
<form method="post" hx-boost="true" action="{{.Path}}">
<div class="field">
<label for="password" class="label">Password</label>
<div class="control">
<input type="password" id="password" name="password" placeholder="*******" class="input {{.Form.GetFieldStatusClass "Password"}}">
{{template "field-errors" (.Form.GetFieldErrors "Password")}}
</div>
</div>
<div class="field">
<label for="password-confirm" class="label">Confirm password</label>
<div class="control">
<input type="password" id="password-confirm" name="password-confirm" placeholder="*******" class="input {{.Form.GetFieldStatusClass "ConfirmPassword"}}">
{{template "field-errors" (.Form.GetFieldErrors "ConfirmPassword")}}
</div>
</div>
<div class="field is-grouped">
<p class="control">
<button class="button is-primary">Update password</button>
</p>
</div>
{{template "csrf" .}}
</form>
{{end}}

View File

@ -1,5 +0,0 @@
{{define "content"}}
{{- range .Data}}
<a class="panel-block" href="{{.URL}}">{{.Title}}</a>
{{- end}}
{{end}}

View File

@ -1,43 +0,0 @@
{{define "content"}}
{{- if not (eq .HTMX.Request.Target "task")}}
<article class="message is-link">
<div class="message-body">
<p>Submitting this form will create an <i>ExampleTask</i> in the task queue. After the specified delay, the message will be logged by the queue processor.</p>
<p>See pkg/tasks and the README for more information.</p>
</div>
</article>
{{- end}}
{{template "form" .}}
{{end}}
{{define "form"}}
<form id="task" method="post" hx-post="{{url "task.submit"}}">
{{template "messages" .}}
<div class="field">
<label for="delay" class="label">Delay (in seconds)</label>
<div class="control">
<input type="number" id="delay" name="delay" class="input {{.Form.GetFieldStatusClass "Delay"}}" value="{{.Form.Delay}}"/>
</div>
<p class="help">How long to wait until the task is executed</p>
{{template "field-errors" (.Form.GetFieldErrors "Delay")}}
</div>
<div class="field">
<label for="message" class="label">Message</label>
<div class="control">
<textarea id="message" name="message" class="textarea {{.Form.GetFieldStatusClass "Message"}}">{{.Form.Message}}</textarea>
</div>
<p class="help">The message the task will output to the log</p>
{{template "field-errors" (.Form.GetFieldErrors "Message")}}
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Add task to queue</button>
</div>
</div>
{{template "csrf" .}}
</form>
{{end}}

View File

@ -16,7 +16,6 @@ type (
const ( const (
LayoutMain Layout = "main" LayoutMain Layout = "main"
LayoutAuth Layout = "auth"
LayoutHTMX Layout = "htmx" LayoutHTMX Layout = "htmx"
) )