diff --git a/web/server.go b/web/server.go
index c15c326..a10bc50 100644
--- a/web/server.go
+++ b/web/server.go
@@ -29,16 +29,17 @@ var content embed.FS
// Server represents the web server for the derby clock
type Server struct {
- router *chi.Mux
- clock *derby.DerbyClock
- events <-chan derby.Event
- clients map[chan string]bool
- clientsMux sync.Mutex
- port int
- server *http.Server
- shutdown chan struct{}
- logger *slog.Logger
- db *db.DB
+ router *chi.Mux
+ clock *derby.DerbyClock
+ events <-chan derby.Event
+ clients map[chan string]bool
+ clientsMux sync.Mutex
+ port int
+ server *http.Server
+ shutdown chan struct{}
+ logger *slog.Logger
+ db *db.DB
+ adminEvents chan string
}
// NewServer creates a new web server
@@ -51,15 +52,16 @@ func NewServer(clock *derby.DerbyClock, events <-chan derby.Event, dbPath string
// Create server
s := &Server{
- router: chi.NewRouter(),
- clock: clock,
- events: events,
- clients: make(map[chan string]bool),
- clientsMux: sync.Mutex{},
- port: port,
- shutdown: make(chan struct{}),
- logger: logger,
- db: database,
+ router: chi.NewRouter(),
+ clock: clock,
+ events: events,
+ clients: make(map[chan string]bool),
+ clientsMux: sync.Mutex{},
+ port: port,
+ shutdown: make(chan struct{}),
+ logger: logger,
+ db: database,
+ adminEvents: make(chan string, 10),
}
// Set up routes
@@ -145,6 +147,9 @@ func (s *Server) routes() {
// Add racers list route
s.router.Get("/admin/racers/list", s.handleRacersList())
+
+ // Add admin events route
+ s.router.Get("/api/admin-events", s.handleAdminEvents())
}
// Start starts the web server
@@ -440,7 +445,7 @@ func (s *Server) handleRegister() http.HandlerFunc {
// handleRegisterForm returns just the registration form component
func (s *Server) handleRegisterForm() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
- // Get groups for the form
+ // Get groups
groups, err := s.db.GetGroups()
if err != nil {
s.logger.Error("Failed to get groups", "error", err)
@@ -448,12 +453,11 @@ func (s *Server) handleRegisterForm() http.HandlerFunc {
return
}
- // Render just the registration form component
- component := templates.RegisterForm(groups)
+ // Render form with isAdmin=true
+ component := templates.RegisterForm(groups, true)
if err := component.Render(r.Context(), w); err != nil {
- s.logger.Error("Failed to render registration form", "error", err)
- http.Error(w, "Failed to render registration form", http.StatusInternalServerError)
- return
+ s.logger.Error("Failed to render form", "error", err)
+ http.Error(w, "Failed to render form", http.StatusInternalServerError)
}
}
}
@@ -607,6 +611,14 @@ func (s *Server) handleCreateRacer() http.HandlerFunc {
s.logger.Info("Racer created", "id", id)
+ // Broadcast event to admin page
+ select {
+ case s.adminEvents <- "racer-added":
+ // Event sent
+ default:
+ // Channel full, non-blocking
+ }
+
// Return success message
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(`
Racer added successfully!
`))
@@ -873,3 +885,26 @@ func (s *Server) handleRacersList() http.HandlerFunc {
}
}
}
+
+// handleAdminEvents handles admin events with SSE
+func (s *Server) handleAdminEvents() http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ // Set SSE headers
+ w.Header().Set("Content-Type", "text/event-stream")
+ w.Header().Set("Cache-Control", "no-cache")
+ w.Header().Set("Connection", "keep-alive")
+
+ // Send events to client
+ for {
+ select {
+ case event := <-s.adminEvents:
+ fmt.Fprintf(w, "data: %s\n\n", event)
+ if f, ok := w.(http.Flusher); ok {
+ f.Flush()
+ }
+ case <-r.Context().Done():
+ return
+ }
+ }
+ }
+}
diff --git a/web/templates/admin.templ b/web/templates/admin.templ
index e189003..d81ce63 100644
--- a/web/templates/admin.templ
+++ b/web/templates/admin.templ
@@ -188,6 +188,15 @@ templ Admin(groups []models.Group, racers []models.Racer) {
// Update submit button
form.querySelector('button[type="submit"]').textContent = 'Update Group';
}
+
+ document.addEventListener('DOMContentLoaded', function() {
+ const evtSource = new EventSource("/api/admin-events");
+ evtSource.onmessage = function(event) {
+ if (event.data === "racer-added") {
+ htmx.ajax('GET', '/admin/racers/list', {target: '#racers-list'});
+ }
+ };
+ });
}
}
diff --git a/web/templates/admin_templ.go b/web/templates/admin_templ.go
index b967459..3defce7 100644
--- a/web/templates/admin_templ.go
+++ b/web/templates/admin_templ.go
@@ -236,7 +236,7 @@ func Admin(groups []models.Group, racers []models.Racer) templ.Component {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -298,7 +298,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component {
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(racer.FirstName + " " + racer.LastName)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 223, Col: 51}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 232, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
@@ -311,7 +311,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component {
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(racer.CarNumber)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 224, Col: 28}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 233, Col: 28}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
@@ -324,7 +324,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component {
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(getGroupNameForRacer(groups, racer.GroupID))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 225, Col: 56}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 234, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
@@ -337,7 +337,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component {
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs("/admin/racers/edit/" + strconv.FormatInt(racer.ID, 10))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 230, Col: 74}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 239, Col: 74}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
@@ -350,7 +350,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component {
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("/api/racers/" + strconv.FormatInt(racer.ID, 10))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 237, Col: 70}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 246, Col: 70}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
@@ -363,7 +363,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component {
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs("Are you sure you want to delete " + racer.FirstName + " " + racer.LastName + "?")
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 239, Col: 104}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/admin.templ`, Line: 248, Col: 104}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
diff --git a/web/templates/register.templ b/web/templates/register.templ
index fe99ea9..c3f6194 100644
--- a/web/templates/register.templ
+++ b/web/templates/register.templ
@@ -1,137 +1,30 @@
package templates
import (
- "fmt"
"strconv"
"track-gopher/models"
)
templ Register(groups []models.Group) {
-
-
-
-
-
- Racer Registration
-
-
-
-
-
-
-
+ @Layout("Register Racer") {
-
+
Register for Derby Race
-
-
-
-
-
-
Registration Successful!
-
The racer has been registered successfully.
-
-
-
-
View All Racers
-
-
-
-
-
Registration Failed
-
There was an error registering the racer.
-
-
+
+
+
+ @RegisterForm(groups, false)
-
-
-
-
+ }
}
-templ RegisterForm(groups []models.Group) {
+templ RegisterForm(groups []models.Group, isAdmin bool) {
@@ -144,7 +37,10 @@ templ RegisterForm(groups []models.Group) {
document.getElementById('racer-form').reset();
setTimeout(function() {
document.getElementById('racer-form-message').innerHTML = '';
- loadRacersList();
+ // Check if we're on the admin page by looking for the racers-list element
+ if (document.getElementById('racers-list')) {
+ htmx.ajax('GET', '/admin/racers/list', {target: '#racers-list'});
+ }
}, 3000);
}"
>
@@ -175,16 +71,14 @@ templ RegisterForm(groups []models.Group) {
-
-
+ if isAdmin {
+
+
+ } else {
+
+ }
-
-
}
\ No newline at end of file
diff --git a/web/templates/register_templ.go b/web/templates/register_templ.go
index c4d7a55..bbd5f14 100644
--- a/web/templates/register_templ.go
+++ b/web/templates/register_templ.go
@@ -9,7 +9,6 @@ import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
- "fmt"
"strconv"
"track-gopher/models"
)
@@ -35,43 +34,33 @@ func Register(groups []models.Group) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
Racer RegistrationRegistration Successful!
The racer has been registered successfully.
Registration Failed
There was an error registering the racer.
")
+ return nil
+ })
+ templ_7745c5c3_Err = Layout("Register Racer").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -79,7 +68,7 @@ func Register(groups []models.Group) templ.Component {
})
}
-func RegisterForm(groups []models.Group) templ.Component {
+func RegisterForm(groups []models.Group, isAdmin bool) 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 {
@@ -95,48 +84,63 @@ func RegisterForm(groups []models.Group) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var4 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var4 == nil {
- templ_7745c5c3_Var4 = templ.NopComponent
+ templ_7745c5c3_Var3 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var3 == nil {
+ templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}