--- /dev/null
+package stdpart
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+
+ "git.earlybird.gay/today-engine/part"
+ "git.earlybird.gay/today-engine/render"
+)
+
+var (
+ ErrFormValidation = errors.New("form input is invalid")
+)
+
+func Err(base error, reason string) error {
+ return errors.Join(base, errors.New(reason))
+}
+
+type ContactFormResponse struct {
+ ID string
+ Name string
+ Contact string
+ Category string
+ Content string
+}
+
+func parseContactFormResponse(r *http.Request) (ContactFormResponse, error) {
+ resp := ContactFormResponse{}
+ err := r.ParseForm()
+ if err != nil {
+ return resp, err
+ }
+ f := r.Form
+
+ resp.ID = f.Get("id")
+ resp.Name = f.Get("name")
+ resp.Contact = f.Get("contact")
+ resp.Category = f.Get("category")
+ resp.Content = f.Get("content")
+
+ return resp, nil
+}
+
+func ContactForm(categories []string) *part.Part {
+ return part.New("stdp-contact-form", "contact_form.html",
+ part.OnLoad(func(data render.Data) error {
+ data.Set("categories", categories)
+
+ // Set message if one is given for this part
+ r := data.Request()
+ q := r.URL.Query()
+ id := data.ID()
+
+ if q.Get("resp_to") == id {
+ message, err := url.QueryUnescape(q.Get("message"))
+ if err != nil {
+ message = "invalid response message from server"
+ }
+ data.Set("message", map[string]any{
+ "success": q.Get("success") == "true",
+ "message": message,
+ })
+ }
+ return nil
+ }),
+ )
+}
+
+func HandleContactForm(validate, handle func(ContactFormResponse) error) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ resp, err := parseContactFormResponse(r)
+ callback := r.FormValue("callback")
+ respond := func(ok bool, msg string) {
+ msg = url.QueryEscape(msg)
+ w.Header().Set("Location", fmt.Sprintf("%s?resp_to=%s&success=%t&message=%s", callback, resp.ID, ok, msg))
+ w.WriteHeader(http.StatusSeeOther)
+ }
+ if err != nil {
+ respond(false, "unexpected error, please try again later")
+ return
+ }
+
+ if validate != nil {
+ err = validate(resp)
+ if err != nil {
+ respond(false, err.Error())
+ }
+ }
+
+ err = handle(resp)
+ if err != nil {
+ respond(false, "unexpected error, please try again later")
+ }
+
+ respond(true, "message sent")
+ })
+}
+
+func PrintContact(resp ContactFormResponse) error {
+ fmt.Printf("%+v\n", resp)
+ return nil
+}
--- /dev/null
+<template>
+ {{ $id := .id }}
+ <form action="{{.action}}"
+ {{ with .method }}method="{{.}}"{{ end }}>
+ <input type="hidden" id="{{$id}}-callback" name="callback" value="{{.request.URL.Path}}">
+ <input type="hidden" id="{{$id}}-id" name="id" value="{{.id}}">
+ <label for="{{$id}}-name">Name:</label>
+ <input type="text" name="name" id="{{$id}}-name">
+ <label for="{{$id}}-contact">Contact Info:</label>
+ <input type="text" name="contact" id="{{$id}}-contact">
+ {{- with .categories }}
+ <label for="{{$id}}-category">Category:</label>
+ <select name="category" id="{{$id}}-category">
+ <option value="">--Select a category--</option>
+ {{- range . }}
+ <option value="{{.}}">{{.}}</option>
+ {{ end -}}
+ </select>
+ {{ end -}}
+ <label for="{{$id}}-content">Message:</label>
+ <textarea name="content" id="{{$id}}-content"></textarea>
+ <input type="submit">
+ </form>
+ {{- with .message }}
+ <p class="{{ if .success }}success{{ else }}error{{ end }}">{{ .message }}</p>
+ {{ end -}}
+</template>
\ No newline at end of file