diff --git a/features/admin/handlers.go b/features/admin/handlers.go index dede624..0f5934e 100644 --- a/features/admin/handlers.go +++ b/features/admin/handlers.go @@ -12,6 +12,7 @@ import ( "fmt" "log/slog" "net/http" + "net/url" "strconv" "strings" "time" @@ -257,6 +258,7 @@ func (h *Handlers) AdminEventEditPage(w http.ResponseWriter, r *http.Request) { StartTime: ev.StartTime.Format("2006-01-02T15:04"), EndTime: ev.EndTime.Format("2006-01-02T15:04"), ExpectedAttendees: ev.ExpectedAttendees, + Links: toEventLinks(ev.Links), QRToken: token, } @@ -266,8 +268,23 @@ func (h *Handlers) AdminEventEditPage(w http.ResponseWriter, r *http.Request) { } } -// AdminEventEditSave handles POST /admin/events/{token}/edit. +// toEventLinks maps stored event links to the view type. +func toEventLinks(links []event.Link) []pages.EventLink { + out := make([]pages.EventLink, 0, len(links)) + for _, l := range links { + out = append(out, pages.EventLink{Label: l.Label, URL: l.URL}) + } + return out +} + +// AdminEventEditSave handles POST /admin/events/{token}/edit. It rewrites both +// the organizer's PDS record and the local cache so the two stay in sync. func (h *Handlers) AdminEventEditSave(w http.ResponseWriter, r *http.Request) { + _, viewerSess, ok := h.Auth.RequireSession(w, r) + if !ok { + return + } + token := chi.URLParam(r, "token") ev, err := event.LookupByQRToken(r.Context(), h.DB, token) if err != nil { @@ -285,12 +302,16 @@ func (h *Handlers) AdminEventEditSave(w http.ResponseWriter, r *http.Request) { return } - if err := event.UpdateLocal(r.Context(), h.DB, ev.URI, event.UpdateInput{ + // Geofence isn't editable in the form, so carry the existing value through + // to avoid dropping it when the PDS record is rewritten. + if err := event.Update(r.Context(), viewerSess, h.DB, ev.URI, event.UpdateInput{ Name: in.Name, StartTime: in.StartTime, EndTime: in.EndTime, Location: in.Location, ExpectedAttendees: in.ExpectedAttendees, + Geofence: ev.Geofence, + Links: in.Links, }); err != nil { slog.Error("admin event edit save", "err", err) formView.Error = "failed to save: " + err.Error() @@ -492,6 +513,23 @@ func parseEventForm(r *http.Request) (event.CreateInput, pages.AdminEventFormVie ExpectedAttendees: in.ExpectedAttendees, } + // Parse up to MaxEventLinks {label,url} rows. A row needs a URL to be + // kept (the label is optional and falls back to the host at render); + // fully-empty rows are dropped. URLs must be http(s). + for i := 0; i < event.MaxEventLinks; i++ { + label := strings.TrimSpace(r.FormValue("link_label_" + strconv.Itoa(i))) + raw := strings.TrimSpace(r.FormValue("link_url_" + strconv.Itoa(i))) + if raw == "" { + continue + } + u, perr := url.Parse(raw) + if perr != nil || (u.Scheme != "http" && u.Scheme != "https") { + return in, view, "links must be http or https URLs" + } + in.Links = append(in.Links, event.Link{Label: label, URL: raw}) + view.Links = append(view.Links, pages.EventLink{Label: label, URL: raw}) + } + if in.Name == "" { return in, view, "event name is required" } diff --git a/features/admin/handlers_test.go b/features/admin/handlers_test.go new file mode 100644 index 0000000..45635df --- /dev/null +++ b/features/admin/handlers_test.go @@ -0,0 +1,66 @@ +package admin + +import ( + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" +) + +func formRequest(values url.Values) *http.Request { + req := httptest.NewRequest("POST", "/admin/events", strings.NewReader(values.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + return req +} + +func baseEventForm() url.Values { + v := url.Values{} + v.Set("name", "CascadiaJS") + v.Set("start_time", "2026-05-31T09:00") + v.Set("end_time", "2026-05-31T17:00") + return v +} + +func TestParseEventForm_ParsesLinks(t *testing.T) { + v := baseEventForm() + v.Set("link_label_0", "Schedule") + v.Set("link_url_0", "https://example.com/s") + v.Set("link_label_1", "") // empty label, has URL -> kept (host fallback at render) + v.Set("link_url_1", "https://example.com/map") + v.Set("link_label_2", "Ghost") // label only, no URL -> dropped + v.Set("link_url_2", "") + + in, _, errMsg := parseEventForm(formRequest(v)) + if errMsg != "" { + t.Fatalf("unexpected error: %s", errMsg) + } + if len(in.Links) != 2 { + t.Fatalf("got %d links, want 2: %+v", len(in.Links), in.Links) + } + if in.Links[0].Label != "Schedule" || in.Links[0].URL != "https://example.com/s" { + t.Errorf("link[0] = %+v", in.Links[0]) + } + if in.Links[1].URL != "https://example.com/map" { + t.Errorf("link[1] = %+v", in.Links[1]) + } +} + +func TestParseEventForm_RejectsNonHTTPLink(t *testing.T) { + v := baseEventForm() + v.Set("link_url_0", "javascript:alert(1)") + _, _, errMsg := parseEventForm(formRequest(v)) + if errMsg == "" { + t.Fatal("expected error for non-http link, got none") + } +} + +func TestParseEventForm_NoLinks(t *testing.T) { + in, _, errMsg := parseEventForm(formRequest(baseEventForm())) + if errMsg != "" { + t.Fatalf("unexpected error: %s", errMsg) + } + if len(in.Links) != 0 { + t.Errorf("expected no links, got %+v", in.Links) + } +} diff --git a/features/admin/pages/events.templ b/features/admin/pages/events.templ index a267e64..f3d7757 100644 --- a/features/admin/pages/events.templ +++ b/features/admin/pages/events.templ @@ -1,6 +1,10 @@ package pages -import "atmoquest/features/common/layouts" +import ( + "strconv" + + "atmoquest/features/common/layouts" +) // AdminEventsView is the data for /admin/events. type AdminEventsView struct { @@ -91,11 +95,19 @@ type AdminEventFormView struct { StartTime string // datetime-local "2006-01-02T15:04" EndTime string ExpectedAttendees int + Links []EventLink Error string // Edit mode fields (empty for create). QRToken string // non-empty = editing } +// EventLink is a {label, url} pair shown in the admin event form's links +// editor. Mirrors event.Link for the view layer. +type EventLink struct { + Label string + URL string +} + templ AdminEventNew(v AdminEventFormView) { @layouts.Base("admin · new event", "Create a new atmo.quest event.") { @adminShell("events", "") { @@ -130,6 +142,7 @@ templ AdminEventNew(v AdminEventFormView) { + @eventLinksFieldset(v.Links)
cancel @@ -177,18 +190,63 @@ templ AdminEventEdit(v AdminEventFormView) {
+ @eventLinksFieldset(v.Links)
cancel

- changes are saved to the local database only. the PDS record is not modified. + changes are written to your quest.atmo.event PDS record and the local cache.

} } } +// eventLinksFieldset renders the "links (up to 5)" editor, mirroring the +// profile links editor. Always renders MaxEventLinks rows so an organizer can +// add new links; populated rows are pre-filled from v.Links. +templ eventLinksFieldset(links []EventLink) { + +} + +// eventLinkRow renders a single label+URL row of the links editor. +templ eventLinkRow(idx int, l EventLink) { + +} + +// eventLinkAt returns the link at index i, or a zero EventLink if out of range. +func eventLinkAt(links []EventLink, i int) EventLink { + if i < len(links) { + return links[i] + } + return EventLink{} +} + // AdminEventBadgeView feeds the badge designer page. type AdminEventBadgeView struct { EventURI string diff --git a/features/admin/pages/events_templ.go b/features/admin/pages/events_templ.go index b79c619..bf42da2 100644 --- a/features/admin/pages/events_templ.go +++ b/features/admin/pages/events_templ.go @@ -8,7 +8,11 @@ package pages import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" -import "atmoquest/features/common/layouts" +import ( + "strconv" + + "atmoquest/features/common/layouts" +) // AdminEventsView is the data for /admin/events. type AdminEventsView struct { @@ -95,7 +99,7 @@ func AdminEvents(v AdminEventsView) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(ev.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 48, Col: 27} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 52, Col: 27} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -108,7 +112,7 @@ func AdminEvents(v AdminEventsView) templ.Component { var templ_7745c5c3_Var5 templ.SafeURL templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(ev.QRPublicURL)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 50, Col: 64} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 54, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -121,7 +125,7 @@ func AdminEvents(v AdminEventsView) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(ev.QRPublicURL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 50, Col: 113} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 54, Col: 113} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -135,7 +139,7 @@ func AdminEvents(v AdminEventsView) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(ev.Location) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 55, Col: 24} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 59, Col: 24} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -154,7 +158,7 @@ func AdminEvents(v AdminEventsView) templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(ev.StartTime) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 60, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 64, Col: 48} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -167,7 +171,7 @@ func AdminEvents(v AdminEventsView) templ.Component { var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(ev.EndTime) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 60, Col: 71} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 64, Col: 71} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -180,7 +184,7 @@ func AdminEvents(v AdminEventsView) templ.Component { var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(intStr(ev.ExpectedAttendees)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 61, Col: 55} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 65, Col: 55} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -193,7 +197,7 @@ func AdminEvents(v AdminEventsView) templ.Component { var templ_7745c5c3_Var11 templ.SafeURL templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/admin/events/" + ev.QRToken + "/edit")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 64, Col: 104} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 68, Col: 104} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -206,7 +210,7 @@ func AdminEvents(v AdminEventsView) templ.Component { var templ_7745c5c3_Var12 templ.SafeURL templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/e/" + ev.QRToken + "/qr.svg")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 65, Col: 95} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 69, Col: 95} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -219,7 +223,7 @@ func AdminEvents(v AdminEventsView) templ.Component { var templ_7745c5c3_Var13 templ.SafeURL templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/admin/events/" + ev.QRToken + "/badge")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 66, Col: 105} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 70, Col: 105} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -232,7 +236,7 @@ func AdminEvents(v AdminEventsView) templ.Component { var templ_7745c5c3_Var14 templ.SafeURL templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/admin/events/" + ev.QRToken + "/finalize")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 67, Col: 98} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 71, Col: 98} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -245,7 +249,7 @@ func AdminEvents(v AdminEventsView) templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(ev.URI) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 71, Col: 58} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 75, Col: 58} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15) if templ_7745c5c3_Err != nil { @@ -285,11 +289,19 @@ type AdminEventFormView struct { StartTime string // datetime-local "2006-01-02T15:04" EndTime string ExpectedAttendees int + Links []EventLink Error string // Edit mode fields (empty for create). QRToken string // non-empty = editing } +// EventLink is a {label, url} pair shown in the admin event form's links +// editor. Mirrors event.Link for the view layer. +type EventLink struct { + Label string + URL string +} + func AdminEventNew(v AdminEventFormView) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context @@ -347,7 +359,7 @@ func AdminEventNew(v AdminEventFormView) templ.Component { var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(v.Error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 106, Col: 63} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 118, Col: 63} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { @@ -365,7 +377,7 @@ func AdminEventNew(v AdminEventFormView) templ.Component { var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.ResolveAttributeValue(v.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 113, Col: 91} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 125, Col: 91} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var20) if templ_7745c5c3_Err != nil { @@ -378,7 +390,7 @@ func AdminEventNew(v AdminEventFormView) templ.Component { var templ_7745c5c3_Var21 string templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.ResolveAttributeValue(v.Location) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 117, Col: 94} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 129, Col: 94} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var21) if templ_7745c5c3_Err != nil { @@ -391,7 +403,7 @@ func AdminEventNew(v AdminEventFormView) templ.Component { var templ_7745c5c3_Var22 string templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.ResolveAttributeValue(v.StartTime) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 122, Col: 98} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 134, Col: 98} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var22) if templ_7745c5c3_Err != nil { @@ -404,7 +416,7 @@ func AdminEventNew(v AdminEventFormView) templ.Component { var templ_7745c5c3_Var23 string templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.ResolveAttributeValue(v.EndTime) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 126, Col: 92} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 138, Col: 92} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23) if templ_7745c5c3_Err != nil { @@ -417,13 +429,21 @@ func AdminEventNew(v AdminEventFormView) templ.Component { var templ_7745c5c3_Var24 string templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.ResolveAttributeValue(intStr(v.ExpectedAttendees)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 131, Col: 115} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 143, Col: 115} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var24) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\">
cancel

creating an event writes a quest.atmo.event record to your PDS. you'll be taken to the badge designer next.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = eventLinksFieldset(v.Links).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
cancel

creating an event writes a quest.atmo.event record to your PDS. you'll be taken to the badge designer next.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -488,108 +508,116 @@ func AdminEventEdit(v AdminEventFormView) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "

edit event

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "

edit event

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if v.Error != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
heads up: ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
heads up: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var28 string templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(v.Error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 153, Col: 63} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 166, Col: 63} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
cancel

changes are saved to the local database only. the PDS record is not modified.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "\">
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = eventLinksFieldset(v.Links).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
cancel

changes are written to your quest.atmo.event PDS record and the local cache.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -609,6 +637,177 @@ func AdminEventEdit(v AdminEventFormView) templ.Component { }) } +// eventLinksFieldset renders the "links (up to 5)" editor, mirroring the +// profile links editor. Always renders MaxEventLinks rows so an organizer can +// add new links; populated rows are pre-filled from v.Links. +func eventLinksFieldset(links []EventLink) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var35 := templ.GetChildren(ctx) + if templ_7745c5c3_Var35 == nil { + templ_7745c5c3_Var35 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "
links (up to 5) ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for i := 0; i < 5; i++ { + templ_7745c5c3_Err = eventLinkRow(i, eventLinkAt(links, i)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "

shown on the event page above the progress bar. label + URL. http(s) only.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// eventLinkRow renders a single label+URL row of the links editor. +func eventLinkRow(idx int, l EventLink) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var36 := templ.GetChildren(ctx) + if templ_7745c5c3_Var36 == nil { + templ_7745c5c3_Var36 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// eventLinkAt returns the link at index i, or a zero EventLink if out of range. +func eventLinkAt(links []EventLink, i int) EventLink { + if i < len(links) { + return links[i] + } + return EventLink{} +} + // AdminEventBadgeView feeds the badge designer page. type AdminEventBadgeView struct { EventURI string @@ -662,12 +861,12 @@ func AdminEventBadge(v AdminEventBadgeView) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var35 := templ.GetChildren(ctx) - if templ_7745c5c3_Var35 == nil { - templ_7745c5c3_Var35 = templ.NopComponent + templ_7745c5c3_Var44 := templ.GetChildren(ctx) + if templ_7745c5c3_Var44 == nil { + templ_7745c5c3_Var44 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var36 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var45 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -679,7 +878,7 @@ func AdminEventBadge(v AdminEventBadgeView) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var37 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var46 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -691,140 +890,140 @@ func AdminEventBadge(v AdminEventBadgeView) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "

badge designer — ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "

badge designer — ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var38 string - templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(v.EventName) + var templ_7745c5c3_Var47 string + templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(v.EventName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 232, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 290, Col: 67} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if len(v.EventBadges) > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "
Event badges — click to design
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
Event badges — click to design
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, eb := range v.EventBadges { - var templ_7745c5c3_Var39 = []any{"settings-row", templ.KV("active-badge-type", eb.Slug == v.SelectedBadgeType)} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var39...) + var templ_7745c5c3_Var48 = []any{"settings-row", templ.KV("active-badge-type", eb.Slug == v.SelectedBadgeType)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var48...) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if eb.Designed { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "◆") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "◆") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "◇") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "◇") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var42 string - templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(eb.Name) + var templ_7745c5c3_Var51 string + templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinStringErrs(eb.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 248, Col: 50} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 306, Col: 50} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var43 string - templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(eb.Description) + var templ_7745c5c3_Var52 string + templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(eb.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 249, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 307, Col: 56} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if eb.Designed { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var44 string - templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(eb.Label) + var templ_7745c5c3_Var53 string + templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(eb.Label) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 252, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 310, Col: 57} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "
not designed
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "
not designed
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -832,138 +1031,138 @@ func AdminEventBadge(v AdminEventBadgeView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var45 string - templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(v.EventURI) + var templ_7745c5c3_Var54 string + templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(v.EventURI) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 266, Col: 25} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 324, Col: 25} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if v.Design.Signature != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "

● signed (key ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "

● signed (key ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var46 string - templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(v.Design.SigningKeyID) + var templ_7745c5c3_Var55 string + templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(v.Design.SigningKeyID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 270, Col: 70} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 328, Col: 70} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, ")

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, ")

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, s := range v.Shapes { if s == v.Design.Shape { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -979,71 +1178,71 @@ func AdminEventBadge(v AdminEventBadgeView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "
shown when someone views their badge
← all events

public scan URL: shown when someone views their badge

← all events

public scan URL: ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var56 string - templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(v.QRPublicURL) + var templ_7745c5c3_Var65 string + templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(v.QRPublicURL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 307, Col: 93} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/admin/pages/events.templ`, Line: 365, Col: 93} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "
saving signs this design with the server's admin key (ed25519). tampering with the row in the database will fail signature verification on next read.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "
saving signs this design with the server's admin key (ed25519). tampering with the row in the database will fail signature verification on next read.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = adminShell("events", "").Render(templ.WithChildren(ctx, templ_7745c5c3_Var37), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = adminShell("events", "").Render(templ.WithChildren(ctx, templ_7745c5c3_Var46), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = layouts.Base("admin · badge designer", "Design an event badge.").Render(templ.WithChildren(ctx, templ_7745c5c3_Var36), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = layouts.Base("admin · badge designer", "Design an event badge.").Render(templ.WithChildren(ctx, templ_7745c5c3_Var45), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1068,128 +1267,128 @@ func colorSwatchField(name, label string, options []string, selected string) tem }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var57 := templ.GetChildren(ctx) - if templ_7745c5c3_Var57 == nil { - templ_7745c5c3_Var57 = templ.NopComponent + templ_7745c5c3_Var66 := templ.GetChildren(ctx) + if templ_7745c5c3_Var66 == nil { + templ_7745c5c3_Var66 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/features/common/layouts/camerabutton.templ b/features/common/layouts/camerabutton.templ new file mode 100644 index 0000000..63e1bc5 --- /dev/null +++ b/features/common/layouts/camerabutton.templ @@ -0,0 +1,19 @@ +package layouts + +// CameraScanButton renders a mobile-only button that opens the device's rear +// camera. It is a plain