DustyP 9 months ago
parent 67889dc595
commit bb6f3dd71f

@ -29,16 +29,17 @@ var content embed.FS
// Server represents the web server for the derby clock // Server represents the web server for the derby clock
type Server struct { type Server struct {
router *chi.Mux router *chi.Mux
clock *derby.DerbyClock clock *derby.DerbyClock
events <-chan derby.Event events <-chan derby.Event
clients map[chan string]bool clients map[chan string]bool
clientsMux sync.Mutex clientsMux sync.Mutex
port int port int
server *http.Server server *http.Server
shutdown chan struct{} shutdown chan struct{}
logger *slog.Logger logger *slog.Logger
db *db.DB db *db.DB
adminEvents chan string
} }
// NewServer creates a new web server // NewServer creates a new web server
@ -51,15 +52,16 @@ func NewServer(clock *derby.DerbyClock, events <-chan derby.Event, dbPath string
// Create server // Create server
s := &Server{ s := &Server{
router: chi.NewRouter(), router: chi.NewRouter(),
clock: clock, clock: clock,
events: events, events: events,
clients: make(map[chan string]bool), clients: make(map[chan string]bool),
clientsMux: sync.Mutex{}, clientsMux: sync.Mutex{},
port: port, port: port,
shutdown: make(chan struct{}), shutdown: make(chan struct{}),
logger: logger, logger: logger,
db: database, db: database,
adminEvents: make(chan string, 10),
} }
// Set up routes // Set up routes
@ -145,6 +147,9 @@ func (s *Server) routes() {
// Add racers list route // Add racers list route
s.router.Get("/admin/racers/list", s.handleRacersList()) 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 // Start starts the web server
@ -440,7 +445,7 @@ func (s *Server) handleRegister() http.HandlerFunc {
// handleRegisterForm returns just the registration form component // handleRegisterForm returns just the registration form component
func (s *Server) handleRegisterForm() http.HandlerFunc { func (s *Server) handleRegisterForm() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
// Get groups for the form // Get groups
groups, err := s.db.GetGroups() groups, err := s.db.GetGroups()
if err != nil { if err != nil {
s.logger.Error("Failed to get groups", "error", err) s.logger.Error("Failed to get groups", "error", err)
@ -448,12 +453,11 @@ func (s *Server) handleRegisterForm() http.HandlerFunc {
return return
} }
// Render just the registration form component // Render form with isAdmin=true
component := templates.RegisterForm(groups) component := templates.RegisterForm(groups, true)
if err := component.Render(r.Context(), w); err != nil { if err := component.Render(r.Context(), w); err != nil {
s.logger.Error("Failed to render registration form", "error", err) s.logger.Error("Failed to render form", "error", err)
http.Error(w, "Failed to render registration form", http.StatusInternalServerError) http.Error(w, "Failed to render form", http.StatusInternalServerError)
return
} }
} }
} }
@ -607,6 +611,14 @@ func (s *Server) handleCreateRacer() http.HandlerFunc {
s.logger.Info("Racer created", "id", id) 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 // Return success message
w.Header().Set("Content-Type", "text/html") w.Header().Set("Content-Type", "text/html")
w.Write([]byte(`<div class="alert alert-success">Racer added successfully!</div>`)) w.Write([]byte(`<div class="alert alert-success">Racer added successfully!</div>`))
@ -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
}
}
}
}

@ -188,6 +188,15 @@ templ Admin(groups []models.Group, racers []models.Racer) {
// Update submit button // Update submit button
form.querySelector('button[type="submit"]').textContent = 'Update Group'; 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'});
}
};
});
</script> </script>
} }
} }

@ -236,7 +236,7 @@ func Admin(groups []models.Group, racers []models.Racer) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</div></div></div></div></div></div><script>\r\n\t\t\tfunction toggleGroupForm() {\r\n\t\t\t\tconst formContainer = document.getElementById('group-form-container');\r\n\t\t\t\tformContainer.style.display = formContainer.style.display === 'none' ? 'block' : 'none';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfunction editGroup(id, name, description) {\r\n\t\t\t\t// Show the form\r\n\t\t\t\tconst formContainer = document.getElementById('group-form-container');\r\n\t\t\t\tformContainer.style.display = 'block';\r\n\t\t\t\t\r\n\t\t\t\t// Update form to be an edit form\r\n\t\t\t\tconst form = formContainer.querySelector('form');\r\n\t\t\t\tform.setAttribute('hx-post', `/api/groups/${id}`);\r\n\t\t\t\tform.setAttribute('hx-method', 'PUT');\r\n\t\t\t\t\r\n\t\t\t\t// Set form values\r\n\t\t\t\tdocument.getElementById('group-name').value = name;\r\n\t\t\t\tdocument.getElementById('group-description').value = description;\r\n\t\t\t\t\r\n\t\t\t\t// Update header\r\n\t\t\t\tformContainer.querySelector('.card-header h3').textContent = 'Edit Group';\r\n\t\t\t\t\r\n\t\t\t\t// Update submit button\r\n\t\t\t\tform.querySelector('button[type=\"submit\"]').textContent = 'Update Group';\r\n\t\t\t}\r\n\t\t</script>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</div></div></div></div></div></div><script>\r\n\t\t\tfunction toggleGroupForm() {\r\n\t\t\t\tconst formContainer = document.getElementById('group-form-container');\r\n\t\t\t\tformContainer.style.display = formContainer.style.display === 'none' ? 'block' : 'none';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfunction editGroup(id, name, description) {\r\n\t\t\t\t// Show the form\r\n\t\t\t\tconst formContainer = document.getElementById('group-form-container');\r\n\t\t\t\tformContainer.style.display = 'block';\r\n\t\t\t\t\r\n\t\t\t\t// Update form to be an edit form\r\n\t\t\t\tconst form = formContainer.querySelector('form');\r\n\t\t\t\tform.setAttribute('hx-post', `/api/groups/${id}`);\r\n\t\t\t\tform.setAttribute('hx-method', 'PUT');\r\n\t\t\t\t\r\n\t\t\t\t// Set form values\r\n\t\t\t\tdocument.getElementById('group-name').value = name;\r\n\t\t\t\tdocument.getElementById('group-description').value = description;\r\n\t\t\t\t\r\n\t\t\t\t// Update header\r\n\t\t\t\tformContainer.querySelector('.card-header h3').textContent = 'Edit Group';\r\n\t\t\t\t\r\n\t\t\t\t// Update submit button\r\n\t\t\t\tform.querySelector('button[type=\"submit\"]').textContent = 'Update Group';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tdocument.addEventListener('DOMContentLoaded', function() {\r\n\t\t\t\tconst evtSource = new EventSource(\"/api/admin-events\");\r\n\t\t\t\tevtSource.onmessage = function(event) {\r\n\t\t\t\t\tif (event.data === \"racer-added\") {\r\n\t\t\t\t\t\thtmx.ajax('GET', '/admin/racers/list', {target: '#racers-list'});\r\n\t\t\t\t\t}\r\n\t\t\t\t};\r\n\t\t\t});\r\n\t\t</script>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -298,7 +298,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component {
var templ_7745c5c3_Var14 string var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(racer.FirstName + " " + racer.LastName) templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(racer.FirstName + " " + racer.LastName)
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -311,7 +311,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component {
var templ_7745c5c3_Var15 string var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(racer.CarNumber) templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(racer.CarNumber)
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -324,7 +324,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component {
var templ_7745c5c3_Var16 string var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(getGroupNameForRacer(groups, racer.GroupID)) templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(getGroupNameForRacer(groups, racer.GroupID))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -337,7 +337,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component {
var templ_7745c5c3_Var17 string var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs("/admin/racers/edit/" + strconv.FormatInt(racer.ID, 10)) templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs("/admin/racers/edit/" + strconv.FormatInt(racer.ID, 10))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -350,7 +350,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component {
var templ_7745c5c3_Var18 string var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("/api/racers/" + strconv.FormatInt(racer.ID, 10)) templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("/api/racers/" + strconv.FormatInt(racer.ID, 10))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -363,7 +363,7 @@ func RacersList(racers []models.Racer, groups []models.Group) templ.Component {
var templ_7745c5c3_Var19 string var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs("Are you sure you want to delete " + racer.FirstName + " " + racer.LastName + "?") templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs("Are you sure you want to delete " + racer.FirstName + " " + racer.LastName + "?")
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {

@ -1,137 +1,30 @@
package templates package templates
import ( import (
"fmt"
"strconv" "strconv"
"track-gopher/models" "track-gopher/models"
) )
templ Register(groups []models.Group) { templ Register(groups []models.Group) {
<!DOCTYPE html> @Layout("Register Racer") {
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Racer Registration</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="/static/js/htmx.min.js"></script>
<!-- Bootstrap JS Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body class="bg-light">
<div class="container py-4"> <div class="container py-4">
<header class="mb-4"> <h1 class="mb-4">Register for Derby Race</h1>
<h1 class="text-center">Racer Registration</h1>
<div class="d-flex justify-content-center mt-3">
<a href="/" class="btn btn-outline-primary me-2">Race Timer</a>
<a href="/admin" class="btn btn-outline-secondary">Admin</a>
</div>
</header>
<div class="row justify-content-center"> <div class="row">
<div class="col-md-8 col-lg-6"> <div class="col-md-8 mx-auto">
<div class="card"> <div id="registration-container">
<div class="card-header"> @RegisterForm(groups, false)
<h5 class="mb-0">Register New Racer</h5>
</div>
<div class="card-body">
<form id="registerForm" hx-post="/api/racers" hx-swap="none">
<div class="row">
<div class="col-md-6 mb-3">
<label for="firstName" class="form-label">First Name</label>
<input type="text" class="form-control" id="firstName" name="first_name" required/>
</div>
<div class="col-md-6 mb-3">
<label for="lastName" class="form-label">Last Name</label>
<input type="text" class="form-control" id="lastName" name="last_name" required/>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="carNumber" class="form-label">Car Number</label>
<input type="text" class="form-control" id="carNumber" name="car_number" required/>
</div>
<div class="col-md-6 mb-3">
<label for="carWeight" class="form-label">Car Weight (oz)</label>
<input type="number" step="0.1" class="form-control" id="carWeight" name="car_weight" required/>
</div>
</div>
<div class="mb-3">
<label for="groupID" class="form-label">Group</label>
<select class="form-select" id="groupID" name="group_id" required>
<option value="" selected disabled>Select a group</option>
for _, group := range groups {
<option value={ fmt.Sprint(group.ID) }>{ group.Name }</option>
}
</select>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Register Racer</button>
</div>
</form>
</div>
</div>
<div id="successAlert" class="alert alert-success mt-3 d-none">
<h4 class="alert-heading">Registration Successful!</h4>
<p>The racer has been registered successfully.</p>
<hr>
<div class="d-flex justify-content-between">
<button class="btn btn-outline-success" onclick="resetForm()">Register Another Racer</button>
<a href="/admin" class="btn btn-outline-primary">View All Racers</a>
</div>
</div>
<div id="errorAlert" class="alert alert-danger mt-3 d-none">
<h4 class="alert-heading">Registration Failed</h4>
<p id="errorMessage">There was an error registering the racer.</p>
<hr>
<button class="btn btn-outline-danger" onclick="dismissError()">Dismiss</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
}
<script>
document.getElementById('registerForm').addEventListener('htmx:afterRequest', function(event) {
const response = event.detail.xhr;
if (response.status === 200 || response.status === 201) {
// Show success message
document.getElementById('successAlert').classList.remove('d-none');
document.getElementById('errorAlert').classList.add('d-none');
} else {
// Show error message
document.getElementById('errorAlert').classList.remove('d-none');
document.getElementById('successAlert').classList.add('d-none');
try {
const error = JSON.parse(response.responseText);
document.getElementById('errorMessage').textContent = error.message || 'There was an error registering the racer.';
} catch (e) {
document.getElementById('errorMessage').textContent = 'There was an error registering the racer.';
}
}
});
function resetForm() {
document.getElementById('registerForm').reset();
document.getElementById('successAlert').classList.add('d-none');
}
function dismissError() {
document.getElementById('errorAlert').classList.add('d-none');
}
</script>
</body>
</html>
} }
templ RegisterForm(groups []models.Group) { templ RegisterForm(groups []models.Group, isAdmin bool) {
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3 class="h6 mb-0">Add New Racer</h3> <h3 class="h6 mb-0">Racer Registration</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="racer-form-message"></div> <div id="racer-form-message"></div>
@ -144,7 +37,10 @@ templ RegisterForm(groups []models.Group) {
document.getElementById('racer-form').reset(); document.getElementById('racer-form').reset();
setTimeout(function() { setTimeout(function() {
document.getElementById('racer-form-message').innerHTML = ''; 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); }, 3000);
}" }"
> >
@ -175,16 +71,14 @@ templ RegisterForm(groups []models.Group) {
</select> </select>
</div> </div>
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
<button type="button" class="btn btn-secondary me-2" onclick="document.getElementById('racer-form-container').style.display = 'none';">Cancel</button> if isAdmin {
<button type="submit" class="btn btn-primary">Save Racer</button> <button type="button" class="btn btn-secondary me-2" onclick="document.getElementById('racer-form-container').style.display = 'none';">Cancel</button>
<button type="submit" class="btn btn-primary">Save Racer</button>
} else {
<button type="submit" class="btn btn-primary">Register</button>
}
</div> </div>
</form> </form>
</div> </div>
</div> </div>
<script>
function loadRacersList() {
htmx.ajax('GET', '/admin/racers/list', {target: '#racers-list'});
}
</script>
} }

@ -9,7 +9,6 @@ import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime" import templruntime "github.com/a-h/templ/runtime"
import ( import (
"fmt"
"strconv" "strconv"
"track-gopher/models" "track-gopher/models"
) )
@ -35,43 +34,33 @@ func Register(groups []models.Group) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent templ_7745c5c3_Var1 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Racer Registration</title><!-- Bootstrap CSS --><link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script><!-- Bootstrap JS Bundle with Popper --><script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js\"></script></head><body class=\"bg-light\"><div class=\"container py-4\"><header class=\"mb-4\"><h1 class=\"text-center\">Racer Registration</h1><div class=\"d-flex justify-content-center mt-3\"><a href=\"/\" class=\"btn btn-outline-primary me-2\">Race Timer</a> <a href=\"/admin\" class=\"btn btn-outline-secondary\">Admin</a></div></header><div class=\"row justify-content-center\"><div class=\"col-md-8 col-lg-6\"><div class=\"card\"><div class=\"card-header\"><h5 class=\"mb-0\">Register New Racer</h5></div><div class=\"card-body\"><form id=\"registerForm\" hx-post=\"/api/racers\" hx-swap=\"none\"><div class=\"row\"><div class=\"col-md-6 mb-3\"><label for=\"firstName\" class=\"form-label\">First Name</label> <input type=\"text\" class=\"form-control\" id=\"firstName\" name=\"first_name\" required></div><div class=\"col-md-6 mb-3\"><label for=\"lastName\" class=\"form-label\">Last Name</label> <input type=\"text\" class=\"form-control\" id=\"lastName\" name=\"last_name\" required></div></div><div class=\"row\"><div class=\"col-md-6 mb-3\"><label for=\"carNumber\" class=\"form-label\">Car Number</label> <input type=\"text\" class=\"form-control\" id=\"carNumber\" name=\"car_number\" required></div><div class=\"col-md-6 mb-3\"><label for=\"carWeight\" class=\"form-label\">Car Weight (oz)</label> <input type=\"number\" step=\"0.1\" class=\"form-control\" id=\"carWeight\" name=\"car_weight\" required></div></div><div class=\"mb-3\"><label for=\"groupID\" class=\"form-label\">Group</label> <select class=\"form-select\" id=\"groupID\" name=\"group_id\" required><option value=\"\" selected disabled>Select a group</option> ") templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
if templ_7745c5c3_Err != nil { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
return templ_7745c5c3_Err templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
} if !templ_7745c5c3_IsBuffer {
for _, group := range groups { defer func() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<option value=\"") templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err == nil {
return templ_7745c5c3_Err templ_7745c5c3_Err = templ_7745c5c3_BufErr
} }
var templ_7745c5c3_Var2 string }()
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(group.ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/register.templ`, Line: 65, Col: 47}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"container py-4\"><h1 class=\"mb-4\">Register for Derby Race</h1><div class=\"row\"><div class=\"col-md-8 mx-auto\"><div id=\"registration-container\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">") templ_7745c5c3_Err = RegisterForm(groups, false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var3 string templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div></div></div></div>")
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(group.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/register.templ`, Line: 65, Col: 62}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</option>") return nil
if templ_7745c5c3_Err != nil { })
return templ_7745c5c3_Err templ_7745c5c3_Err = Layout("Register Racer").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</select></div><div class=\"d-grid\"><button type=\"submit\" class=\"btn btn-primary\">Register Racer</button></div></form></div></div><div id=\"successAlert\" class=\"alert alert-success mt-3 d-none\"><h4 class=\"alert-heading\">Registration Successful!</h4><p>The racer has been registered successfully.</p><hr><div class=\"d-flex justify-content-between\"><button class=\"btn btn-outline-success\" onclick=\"resetForm()\">Register Another Racer</button> <a href=\"/admin\" class=\"btn btn-outline-primary\">View All Racers</a></div></div><div id=\"errorAlert\" class=\"alert alert-danger mt-3 d-none\"><h4 class=\"alert-heading\">Registration Failed</h4><p id=\"errorMessage\">There was an error registering the racer.</p><hr><button class=\"btn btn-outline-danger\" onclick=\"dismissError()\">Dismiss</button></div></div></div></div><script>\r\n\t\t\tdocument.getElementById('registerForm').addEventListener('htmx:afterRequest', function(event) {\r\n\t\t\t\tconst response = event.detail.xhr;\r\n\t\t\t\t\r\n\t\t\t\tif (response.status === 200 || response.status === 201) {\r\n\t\t\t\t\t// Show success message\r\n\t\t\t\t\tdocument.getElementById('successAlert').classList.remove('d-none');\r\n\t\t\t\t\tdocument.getElementById('errorAlert').classList.add('d-none');\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// Show error message\r\n\t\t\t\t\tdocument.getElementById('errorAlert').classList.remove('d-none');\r\n\t\t\t\t\tdocument.getElementById('successAlert').classList.add('d-none');\r\n\t\t\t\t\t\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tconst error = JSON.parse(response.responseText);\r\n\t\t\t\t\t\tdocument.getElementById('errorMessage').textContent = error.message || 'There was an error registering the racer.';\r\n\t\t\t\t\t} catch (e) {\r\n\t\t\t\t\t\tdocument.getElementById('errorMessage').textContent = 'There was an error registering the racer.';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\tfunction resetForm() {\r\n\t\t\t\tdocument.getElementById('registerForm').reset();\r\n\t\t\t\tdocument.getElementById('successAlert').classList.add('d-none');\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfunction dismissError() {\r\n\t\t\t\tdocument.getElementById('errorAlert').classList.add('d-none');\r\n\t\t\t}\r\n\t\t</script></body></html>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err 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) { 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 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { 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) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx) templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil { if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var4 = templ.NopComponent templ_7745c5c3_Var3 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"card\"><div class=\"card-header\"><h3 class=\"h6 mb-0\">Add New Racer</h3></div><div class=\"card-body\"><div id=\"racer-form-message\"></div><form id=\"racer-form\" hx-post=\"/api/racers\" hx-target=\"#racer-form-message\" hx-swap=\"innerHTML\" hx-on::after-request=\"if(event.detail.successful) { \r\n\t\t\t\t\tdocument.getElementById(&#39;racer-form&#39;).reset(); \r\n\t\t\t\t\tsetTimeout(function() {\r\n\t\t\t\t\t\tdocument.getElementById(&#39;racer-form-message&#39;).innerHTML = &#39;&#39;;\r\n\t\t\t\t\t\tloadRacersList();\r\n\t\t\t\t\t}, 3000);\r\n\t\t\t\t}\"><!-- Form fields for racer registration --><div class=\"mb-3\"><label for=\"first-name\" class=\"form-label\">First Name</label> <input type=\"text\" class=\"form-control\" id=\"first-name\" name=\"first_name\" required></div><div class=\"mb-3\"><label for=\"last-name\" class=\"form-label\">Last Name</label> <input type=\"text\" class=\"form-control\" id=\"last-name\" name=\"last_name\" required></div><div class=\"mb-3\"><label for=\"car-number\" class=\"form-label\">Car Number</label> <input type=\"text\" class=\"form-control\" id=\"car-number\" name=\"car_number\" required></div><div class=\"mb-3\"><label for=\"car-weight\" class=\"form-label\">Car Weight (oz)</label> <input type=\"number\" step=\"0.01\" class=\"form-control\" id=\"car-weight\" name=\"car_weight\" required></div><div class=\"mb-3\"><label for=\"group-id\" class=\"form-label\">Group</label> <select class=\"form-select\" id=\"group-id\" name=\"group_id\" required><option value=\"\">Select a group</option> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"card\"><div class=\"card-header\"><h3 class=\"h6 mb-0\">Racer Registration</h3></div><div class=\"card-body\"><div id=\"racer-form-message\"></div><form id=\"racer-form\" hx-post=\"/api/racers\" hx-target=\"#racer-form-message\" hx-swap=\"innerHTML\" hx-on::after-request=\"if(event.detail.successful) { \r\n\t\t\t\t\tdocument.getElementById(&#39;racer-form&#39;).reset(); \r\n\t\t\t\t\tsetTimeout(function() {\r\n\t\t\t\t\t\tdocument.getElementById(&#39;racer-form-message&#39;).innerHTML = &#39;&#39;;\r\n\t\t\t\t\t\t// Check if we&#39;re on the admin page by looking for the racers-list element\r\n\t\t\t\t\t\tif (document.getElementById(&#39;racers-list&#39;)) {\r\n\t\t\t\t\t\t\thtmx.ajax(&#39;GET&#39;, &#39;/admin/racers/list&#39;, {target: &#39;#racers-list&#39;});\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}, 3000);\r\n\t\t\t\t}\"><!-- Form fields for racer registration --><div class=\"mb-3\"><label for=\"first-name\" class=\"form-label\">First Name</label> <input type=\"text\" class=\"form-control\" id=\"first-name\" name=\"first_name\" required></div><div class=\"mb-3\"><label for=\"last-name\" class=\"form-label\">Last Name</label> <input type=\"text\" class=\"form-control\" id=\"last-name\" name=\"last_name\" required></div><div class=\"mb-3\"><label for=\"car-number\" class=\"form-label\">Car Number</label> <input type=\"text\" class=\"form-control\" id=\"car-number\" name=\"car_number\" required></div><div class=\"mb-3\"><label for=\"car-weight\" class=\"form-label\">Car Weight (oz)</label> <input type=\"number\" step=\"0.01\" class=\"form-control\" id=\"car-weight\" name=\"car_weight\" required></div><div class=\"mb-3\"><label for=\"group-id\" class=\"form-label\">Group</label> <select class=\"form-select\" id=\"group-id\" name=\"group_id\" required><option value=\"\">Select a group</option> ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
for _, group := range groups { for _, group := range groups {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<option value=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<option value=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var5 string var templ_7745c5c3_Var4 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatInt(group.ID, 10)) templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatInt(group.ID, 10))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/register.templ`, Line: 173, Col: 54} return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/register.templ`, Line: 69, Col: 54}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(group.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/register.templ`, Line: 69, Col: 69}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var6 string templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</option>")
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(group.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/register.templ`, Line: 173, Col: 69} return templ_7745c5c3_Err
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</select></div><div class=\"d-flex justify-content-end\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if isAdmin {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<button type=\"button\" class=\"btn btn-secondary me-2\" onclick=\"document.getElementById(&#39;racer-form-container&#39;).style.display = &#39;none&#39;;\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\">Save Racer</button>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</option>") } else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<button type=\"submit\" class=\"btn btn-primary\">Register</button>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</select></div><div class=\"d-flex justify-content-end\"><button type=\"button\" class=\"btn btn-secondary me-2\" onclick=\"document.getElementById(&#39;racer-form-container&#39;).style.display = &#39;none&#39;;\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\">Save Racer</button></div></form></div></div><script>\r\n\t\tfunction loadRacersList() {\r\n\t\t\thtmx.ajax('GET', '/admin/racers/list', {target: '#racers-list'});\r\n\t\t}\r\n\t</script>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div></form></div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

Loading…
Cancel
Save