expand on admin functionality, hardening still needs to be implemented.
This commit is contained in:
68
handlers/admin/dashboard.go
Normal file
68
handlers/admin/dashboard.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
helpers "synlotto-website/helpers"
|
||||
"synlotto-website/models"
|
||||
)
|
||||
|
||||
func AdminDashboardHandler(db *sql.DB) http.HandlerFunc {
|
||||
return helpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
||||
// userID, ok := helpers.GetCurrentUserID(r)
|
||||
// if !ok {
|
||||
// http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
// return
|
||||
// }
|
||||
|
||||
// TODO: check is_admin from users table here
|
||||
|
||||
context := helpers.TemplateContext(w, r)
|
||||
|
||||
// Total ticket stats
|
||||
var total, winners int
|
||||
var prizeSum float64
|
||||
db.QueryRow(`SELECT COUNT(*), SUM(CASE WHEN is_winner THEN 1 ELSE 0 END), SUM(prize_amount) FROM my_tickets`).Scan(&total, &winners, &prizeSum)
|
||||
context["Stats"] = map[string]interface{}{
|
||||
"TotalTickets": total,
|
||||
"TotalWinners": winners,
|
||||
"TotalPrizeAmount": prizeSum,
|
||||
}
|
||||
|
||||
// Match run log
|
||||
rows, err := db.Query(`
|
||||
SELECT run_at, triggered_by, tickets_matched, winners_found, COALESCE(notes, '')
|
||||
FROM log_ticket_matching
|
||||
ORDER BY run_at DESC LIMIT 10
|
||||
`)
|
||||
if err != nil {
|
||||
log.Println("⚠️ Failed to load logs:", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var logs []models.MatchLog
|
||||
for rows.Next() {
|
||||
var logEntry models.MatchLog
|
||||
err := rows.Scan(&logEntry.RunAt, &logEntry.TriggeredBy, &logEntry.TicketsMatched, &logEntry.WinnersFound, &logEntry.Notes)
|
||||
if err != nil {
|
||||
log.Println("⚠️ Failed to scan log row:", err)
|
||||
continue
|
||||
}
|
||||
logs = append(logs, logEntry)
|
||||
}
|
||||
context["MatchLogs"] = logs
|
||||
|
||||
tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles(
|
||||
"templates/layout.html",
|
||||
"templates/admin/dashboard.html",
|
||||
))
|
||||
|
||||
err = tmpl.ExecuteTemplate(w, "layout", context)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to render dashboard", http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
}
|
||||
72
handlers/admin/draws.go
Normal file
72
handlers/admin/draws.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
helpers "synlotto-website/helpers"
|
||||
)
|
||||
|
||||
func NewDrawHandler(db *sql.DB) http.HandlerFunc {
|
||||
return helpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
||||
context := helpers.TemplateContext(w, r)
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
game := r.FormValue("game_type")
|
||||
date := r.FormValue("draw_date")
|
||||
machine := r.FormValue("machine")
|
||||
ballset := r.FormValue("ball_set")
|
||||
|
||||
_, err := db.Exec(`INSERT INTO results_thunderball (game_type, draw_date, machine, ball_set) VALUES (?, ?, ?, ?)`,
|
||||
game, date, machine, ballset)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to add draw", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/admin/dashboard", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := template.Must(template.New("new_draw").Funcs(helpers.TemplateFuncs()).ParseFiles(
|
||||
"templates/layout.html",
|
||||
"templates/admin/draws/new_draw.html",
|
||||
))
|
||||
tmpl.ExecuteTemplate(w, "layout", context)
|
||||
})
|
||||
}
|
||||
|
||||
func ModifyDrawHandler(db *sql.DB) http.HandlerFunc {
|
||||
return helpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost {
|
||||
id := r.FormValue("id")
|
||||
_, err := db.Exec(`UPDATE results_thunderball SET game_type=?, draw_date=?, ball_set=?, machine=? WHERE id=?`,
|
||||
r.FormValue("game_type"), r.FormValue("draw_date"), r.FormValue("ball_set"), r.FormValue("machine"), id)
|
||||
if err != nil {
|
||||
http.Error(w, "Update failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/admin/dashboard", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
// For GET: load draw by ID (pseudo-code)
|
||||
// id := r.URL.Query().Get("id")
|
||||
// query DB, pass into context.Draw
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteDrawHandler(db *sql.DB) http.HandlerFunc {
|
||||
return helpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost {
|
||||
id := r.FormValue("id")
|
||||
_, err := db.Exec(`DELETE FROM results_thunderball WHERE id = ?`, id)
|
||||
if err != nil {
|
||||
http.Error(w, "Delete failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/admin/dashboard", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
70
handlers/admin/prizes.go
Normal file
70
handlers/admin/prizes.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"synlotto-website/helpers"
|
||||
)
|
||||
|
||||
func AddPrizesHandler(db *sql.DB) http.HandlerFunc {
|
||||
return helpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles(
|
||||
"templates/layout.html",
|
||||
"templates/admin/draws/prizes/add_prizes.html",
|
||||
))
|
||||
tmpl.ExecuteTemplate(w, "layout", helpers.TemplateContext(w, r))
|
||||
return
|
||||
}
|
||||
|
||||
drawDate := r.FormValue("draw_date")
|
||||
values := make([]interface{}, 0)
|
||||
for i := 1; i <= 9; i++ {
|
||||
val, _ := strconv.Atoi(r.FormValue(fmt.Sprintf("prize%d_per_winner", i)))
|
||||
values = append(values, val)
|
||||
}
|
||||
|
||||
stmt := `INSERT INTO prizes_thunderball (
|
||||
draw_date, prize1_per_winner, prize2_per_winner, prize3_per_winner,
|
||||
prize4_per_winner, prize5_per_winner, prize6_per_winner,
|
||||
prize7_per_winner, prize8_per_winner, prize9_per_winner
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
|
||||
_, err := db.Exec(stmt, append([]interface{}{drawDate}, values...)...)
|
||||
if err != nil {
|
||||
http.Error(w, "Insert failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/admin/draws", http.StatusSeeOther)
|
||||
})
|
||||
}
|
||||
|
||||
func ModifyPrizesHandler(db *sql.DB) http.HandlerFunc {
|
||||
return helpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles(
|
||||
"templates/layout.html",
|
||||
"templates/admin/draws/prizes/modify_prizes.html",
|
||||
))
|
||||
tmpl.ExecuteTemplate(w, "layout", helpers.TemplateContext(w, r))
|
||||
return
|
||||
}
|
||||
|
||||
drawDate := r.FormValue("draw_date")
|
||||
for i := 1; i <= 9; i++ {
|
||||
key := fmt.Sprintf("prize%d_per_winner", i)
|
||||
val, _ := strconv.Atoi(r.FormValue(key))
|
||||
_, err := db.Exec("UPDATE prizes_thunderball SET "+key+" = ? WHERE draw_date = ?", val, drawDate)
|
||||
if err != nil {
|
||||
http.Error(w, "Update failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/admin/draws", http.StatusSeeOther)
|
||||
})
|
||||
}
|
||||
@@ -107,3 +107,41 @@ func Submit(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func ListDrawsHandler(db *sql.DB) http.HandlerFunc {
|
||||
return helpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
||||
context := helpers.TemplateContext(w, r)
|
||||
draws := []models.DrawSummary{}
|
||||
|
||||
rows, err := db.Query(`
|
||||
SELECT r.id, r.game_type, r.draw_date, r.ball_set, r.machine,
|
||||
(SELECT COUNT(1) FROM prizes_thunderball p WHERE p.draw_date = r.draw_date) as prize_exists
|
||||
FROM results_thunderball r
|
||||
ORDER BY r.draw_date DESC
|
||||
`)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to query draws", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var d models.DrawSummary
|
||||
var prizeFlag int
|
||||
if err := rows.Scan(&d.Id, &d.GameType, &d.DrawDate, &d.BallSet, &d.Machine, &prizeFlag); err != nil {
|
||||
log.Println("⚠️ Draw scan failed:", err)
|
||||
continue
|
||||
}
|
||||
d.PrizeSet = prizeFlag > 0
|
||||
draws = append(draws, d)
|
||||
}
|
||||
|
||||
context["Draws"] = draws
|
||||
|
||||
tmpl := template.Must(template.New("draw_list").Funcs(helpers.TemplateFuncs()).ParseFiles(
|
||||
"templates/layout.html",
|
||||
"templates/admin/draws/list.html",
|
||||
))
|
||||
tmpl.ExecuteTemplate(w, "layout", context)
|
||||
})
|
||||
}
|
||||
|
||||
40
main.go
40
main.go
@@ -1,10 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"net/http"
|
||||
"synlotto-website/handlers"
|
||||
services "synlotto-website/handlers/admin"
|
||||
admin "synlotto-website/handlers/admin"
|
||||
"synlotto-website/helpers"
|
||||
"synlotto-website/middleware"
|
||||
"synlotto-website/models"
|
||||
@@ -24,27 +25,40 @@ func main() {
|
||||
)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
setupAdminRoutes(mux, db)
|
||||
setupAccountRoutes(mux, db)
|
||||
setupResultRoutes(mux, db)
|
||||
|
||||
// Styling
|
||||
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||
|
||||
mux.HandleFunc("/", handlers.Home(db))
|
||||
mux.HandleFunc("/new", handlers.NewDraw) // ToDo: needs to be wrapped in admin auth
|
||||
mux.HandleFunc("/submit", handlers.Submit)
|
||||
|
||||
// Result pages
|
||||
mux.HandleFunc("/results/thunderball", handlers.ResultsThunderball(db))
|
||||
log.Println("🌐 Running on http://localhost:8080")
|
||||
http.ListenAndServe(":8080", helpers.RateLimit(csrfMiddleware(mux)))
|
||||
}
|
||||
|
||||
// Account Pages
|
||||
func setupAdminRoutes(mux *http.ServeMux, db *sql.DB) {
|
||||
mux.HandleFunc("/admin/dashboard", admin.AdminDashboardHandler(db))
|
||||
mux.HandleFunc("/admin/triggers", admin.AdminTriggersHandler(db))
|
||||
|
||||
// Draw management
|
||||
mux.HandleFunc("/admin/draws/new", admin.NewDrawHandler(db))
|
||||
mux.HandleFunc("/admin/draws/modify", admin.ModifyDrawHandler(db))
|
||||
mux.HandleFunc("/admin/draws/delete", admin.DeleteDrawHandler(db))
|
||||
|
||||
// Prize management
|
||||
mux.HandleFunc("/admin/draws/prizes/add", admin.AddPrizesHandler(db))
|
||||
mux.HandleFunc("/admin/draws/prizes/modify", admin.ModifyPrizesHandler(db))
|
||||
}
|
||||
|
||||
func setupAccountRoutes(mux *http.ServeMux, db *sql.DB) {
|
||||
mux.HandleFunc("/login", middleware.Auth(false)(handlers.Login))
|
||||
mux.HandleFunc("/logout", handlers.Logout)
|
||||
mux.HandleFunc("/signup", middleware.Auth(false)(handlers.Signup))
|
||||
mux.HandleFunc("/account/tickets/add_ticket", handlers.AddTicket(db))
|
||||
mux.HandleFunc("/account/tickets/my_tickets", handlers.GetMyTickets(db))
|
||||
|
||||
// Admin Pages
|
||||
mux.HandleFunc("/admin/triggers", services.AdminTriggersHandler(db))
|
||||
|
||||
log.Println("🌐 Running on http://localhost:8080")
|
||||
http.ListenAndServe(":8080", helpers.RateLimit(csrfMiddleware(mux)))
|
||||
}
|
||||
|
||||
func setupResultRoutes(mux *http.ServeMux, db *sql.DB) {
|
||||
mux.HandleFunc("/results/thunderball", handlers.ResultsThunderball(db))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
package models
|
||||
|
||||
type DrawSummary struct {
|
||||
Id int
|
||||
GameType string
|
||||
DrawDate string
|
||||
BallSet string
|
||||
Machine string
|
||||
PrizeSet bool
|
||||
}
|
||||
|
||||
type ThunderballResult struct {
|
||||
Id int
|
||||
DrawDate string
|
||||
|
||||
@@ -38,3 +38,12 @@ type MatchRunStats struct {
|
||||
TicketsMatched int
|
||||
WinnersFound int
|
||||
}
|
||||
|
||||
type MatchLog struct {
|
||||
ID int
|
||||
TriggeredBy string
|
||||
RunAt string
|
||||
TicketsMatched int
|
||||
WinnersFound int
|
||||
Notes string
|
||||
}
|
||||
|
||||
49
templates/admin/dashboard.html
Normal file
49
templates/admin/dashboard.html
Normal file
@@ -0,0 +1,49 @@
|
||||
{{ define "content" }}
|
||||
<h2>📊 Admin Dashboard</h2>
|
||||
<p class="text-sm text-gray-600">Welcome back, admin.</p>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 my-6">
|
||||
<div class="bg-white rounded-xl p-4 shadow">
|
||||
<h3 class="text-sm text-gray-500">Total Tickets</h3>
|
||||
<p class="text-2xl font-bold">{{ .Stats.TotalTickets }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl p-4 shadow">
|
||||
<h3 class="text-sm text-gray-500">Total Winners</h3>
|
||||
<p class="text-2xl font-bold text-green-600">{{ .Stats.TotalWinners }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl p-4 shadow">
|
||||
<h3 class="text-sm text-gray-500">Prize Fund Awarded</h3>
|
||||
<p class="text-2xl font-bold text-blue-600">£{{ printf "%.2f" .Stats.TotalPrizeAmount }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-6">
|
||||
<h3 class="text-lg font-semibold mb-2">Recent Ticket Matches</h3>
|
||||
<table class="w-full text-sm border">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="px-2 py-1">Draw Date</th>
|
||||
<th class="px-2 py-1">Triggered By</th>
|
||||
<th class="px-2 py-1">Matched</th>
|
||||
<th class="px-2 py-1">Winners</th>
|
||||
<th class="px-2 py-1">Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .MatchLogs }}
|
||||
<tr class="border-t">
|
||||
<td class="px-2 py-1">{{ .RunAt }}</td>
|
||||
<td class="px-2 py-1">{{ .TriggeredBy }}</td>
|
||||
<td class="px-2 py-1">{{ .TicketsMatched }}</td>
|
||||
<td class="px-2 py-1">{{ .WinnersFound }}</td>
|
||||
<td class="px-2 py-1 text-xs text-gray-500">{{ .Notes }}</td>
|
||||
</tr>
|
||||
{{ else }}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-2 italic text-gray-400">No match history found</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ end }}
|
||||
9
templates/admin/draws/delete_draw.html
Normal file
9
templates/admin/draws/delete_draw.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{{ define "delete_draw" }}
|
||||
<h2 class="text-xl font-semibold mb-4">Delete Draw</h2>
|
||||
<form method="POST" action="/admin/draws/delete">
|
||||
{{ .CSRFField }}
|
||||
<input type="hidden" name="id" value="{{ .Draw.ID }}">
|
||||
<p>Are you sure you want to delete the draw on <strong>{{ .Draw.DrawDate }}</strong>?</p>
|
||||
<button class="btn bg-red-600 hover:bg-red-700">Delete</button>
|
||||
</form>
|
||||
{{ end }}
|
||||
46
templates/admin/draws/list_draws.html
Normal file
46
templates/admin/draws/list_draws.html
Normal file
@@ -0,0 +1,46 @@
|
||||
{{ define "draw_list" }}
|
||||
<h2 class="text-xl font-bold mb-4">Draws Overview</h2>
|
||||
|
||||
<table class="w-full table-auto border">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Game</th>
|
||||
<th>Date</th>
|
||||
<th>Ball Set</th>
|
||||
<th>Machine</th>
|
||||
<th>Prizes?</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .Draws }}
|
||||
<tr class="border-t">
|
||||
<td>{{ .ID }}</td>
|
||||
<td>{{ .GameType }}</td>
|
||||
<td>{{ .DrawDate }}</td>
|
||||
<td>{{ .BallSet }}</td>
|
||||
<td>{{ .Machine }}</td>
|
||||
<td>
|
||||
{{ if .PrizeSet }}
|
||||
✅
|
||||
{{ else }}
|
||||
❌
|
||||
{{ end }}
|
||||
</td>
|
||||
<td>
|
||||
<a href="/admin/draws/modify?id={{ .ID }}" class="text-blue-600">Edit</a> |
|
||||
<a href="/admin/draws/delete?id={{ .ID }}" class="text-red-600">Delete</a> |
|
||||
{{ if .PrizeSet }}
|
||||
<a href="/admin/draws/prizes/modify?draw_date={{ .DrawDate }}" class="text-green-600">Modify Prizes</a>
|
||||
{{ else }}
|
||||
<a href="/admin/draws/prizes/add?draw_date={{ .DrawDate }}" class="text-gray-600">Add Prizes</a>
|
||||
{{ end }}
|
||||
</td>
|
||||
</tr>
|
||||
{{ else }}
|
||||
<tr><td colspan="7" class="text-center text-gray-500">No draws available.</td></tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ end }}
|
||||
12
templates/admin/draws/modify_draw.html
Normal file
12
templates/admin/draws/modify_draw.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{{ define "modify_draw" }}
|
||||
<h2 class="text-xl font-semibold mb-4">Modify Draw</h2>
|
||||
<form method="POST" action="/admin/draws/modify">
|
||||
{{ .CSRFField }}
|
||||
<input type="hidden" name="id" value="{{ .Draw.ID }}">
|
||||
<label class="block">Game Type: <input name="game_type" class="input" value="{{ .Draw.GameType }}"></label>
|
||||
<label class="block">Draw Date: <input name="draw_date" type="date" class="input" value="{{ .Draw.DrawDate }}"></label>
|
||||
<label class="block">Ball Set: <input name="ball_set" class="input" value="{{ .Draw.BallSet }}"></label>
|
||||
<label class="block">Machine: <input name="machine" class="input" value="{{ .Draw.Machine }}"></label>
|
||||
<button class="btn">Update Draw</button>
|
||||
</form>
|
||||
{{ end }}
|
||||
11
templates/admin/draws/new_draw.html
Normal file
11
templates/admin/draws/new_draw.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{{ define "new_draw" }}
|
||||
<h2 class="text-xl font-semibold mb-4">Add New Draw</h2>
|
||||
<form method="POST" action="/admin/draws/new">
|
||||
{{ .CSRFField }}
|
||||
<label class="block">Game Type: <input name="game_type" class="input"></label>
|
||||
<label class="block">Draw Date: <input name="draw_date" type="date" class="input"></label>
|
||||
<label class="block">Ball Set: <input name="ball_set" class="input"></label>
|
||||
<label class="block">Machine: <input name="machine" class="input"></label>
|
||||
<button class="btn">Create Draw</button>
|
||||
</form>
|
||||
{{ end }}
|
||||
13
templates/admin/draws/prizes/add_prizes.html
Normal file
13
templates/admin/draws/prizes/add_prizes.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{{ define "add_prizes" }}
|
||||
<h2 class="text-xl font-semibold mb-4">Add Prize Breakdown</h2>
|
||||
<form method="POST" action="/admin/draws/prizes/add">
|
||||
{{ .CSRFField }}
|
||||
<input type="hidden" name="draw_date" value="{{ .DrawDate }}">
|
||||
{{ range $i, $ := .PrizeLabels }}
|
||||
<label class="block">{{ . }}
|
||||
<input name="prize{{ add $i 1 }}_per_winner" class="input">
|
||||
</label>
|
||||
{{ end }}
|
||||
<button class="btn">Save Prizes</button>
|
||||
</form>
|
||||
{{ end }}
|
||||
13
templates/admin/draws/prizes/modify_prizes.html
Normal file
13
templates/admin/draws/prizes/modify_prizes.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{{ define "modify_prizes" }}
|
||||
<h2 class="text-xl font-semibold mb-4">Modify Prize Breakdown</h2>
|
||||
<form method="POST" action="/admin/draws/prizes/modify">
|
||||
{{ .CSRFField }}
|
||||
<input type="hidden" name="draw_date" value="{{ .DrawDate }}">
|
||||
{{ range $i, $ := .PrizeLabels }}
|
||||
<label class="block">{{ . }}
|
||||
<input name="prize{{ add $i 1 }}_per_winner" class="input" value="{{ index $.Prizes (print "prize" (add $i 1) "_per_winner") }}">
|
||||
</label>
|
||||
{{ end }}
|
||||
<button class="btn">Update Prizes</button>
|
||||
</form>
|
||||
{{ end }}
|
||||
@@ -15,6 +15,7 @@
|
||||
<p><a href="/login">Login</a></p>
|
||||
{{ end }}
|
||||
</div>
|
||||
<a href="/admin/dashboard" class="hover:underline">Dashboard</a>
|
||||
|
||||
{{ if .Flash }}
|
||||
<div class="flash">{{ .Flash }}</div>
|
||||
|
||||
Reference in New Issue
Block a user