Enhance project functionality: add email template, implement email sending, and improve table management routes
The table management in the database does currently not work
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
|||||||
qrank.db
|
qrank.db
|
||||||
tmp
|
tmp
|
||||||
|
.env
|
||||||
|
notes.txt
|
||||||
@@ -26,7 +26,7 @@ body {
|
|||||||
flex-wrap: wrap
|
flex-wrap: wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn, button {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -24,6 +24,7 @@ require (
|
|||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
@@ -41,6 +42,8 @@ require (
|
|||||||
golang.org/x/sys v0.32.0 // indirect
|
golang.org/x/sys v0.32.0 // indirect
|
||||||
golang.org/x/text v0.24.0 // indirect
|
golang.org/x/text v0.24.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.6 // indirect
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gorm.io/driver/postgres v1.6.0 // indirect
|
gorm.io/driver/postgres v1.6.0 // indirect
|
||||||
gorm.io/driver/sqlite v1.6.0 // indirect
|
gorm.io/driver/sqlite v1.6.0 // indirect
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -57,6 +57,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
|||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
@@ -128,7 +130,11 @@ google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFW
|
|||||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||||
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
8
other/email.txt
Normal file
8
other/email.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Hi {{ .Name }},
|
||||||
|
|
||||||
|
Welcome to QRank! Please confirm your login by clicking this link:
|
||||||
|
|
||||||
|
{{ .Token }}
|
||||||
|
|
||||||
|
Thanks,
|
||||||
|
The QRank Team
|
||||||
15
src/main.go
15
src/main.go
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-contrib/sessions/cookie"
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -34,9 +35,17 @@ const (
|
|||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
err = godotenv.Load()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error loading .env file")
|
||||||
|
}
|
||||||
|
|
||||||
// Get the listening port
|
// Get the listening port
|
||||||
port := os.Getenv("APP_PORT")
|
port := os.Getenv("APP_PORT")
|
||||||
|
if port == "" {
|
||||||
port = strconv.Itoa(defaultPort)
|
port = strconv.Itoa(defaultPort)
|
||||||
|
}
|
||||||
|
|
||||||
// Set the base URL
|
// Set the base URL
|
||||||
baseURL = os.Getenv("APP_BASE_URL")
|
baseURL = os.Getenv("APP_BASE_URL")
|
||||||
@@ -45,7 +54,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Open connection to SQLite
|
// Open connection to SQLite
|
||||||
var err error
|
|
||||||
db, err = gorm.Open(sqlite.Open("qrank.db"), &gorm.Config{})
|
db, err = gorm.Open(sqlite.Open("qrank.db"), &gorm.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Fatal("sqlite connect:", err)
|
lg.Fatal("sqlite connect:", err)
|
||||||
@@ -84,8 +92,9 @@ func main() {
|
|||||||
authorized.POST("/enter", postEnter)
|
authorized.POST("/enter", postEnter)
|
||||||
|
|
||||||
// QR-prepped table routes
|
// QR-prepped table routes
|
||||||
authorized.GET("/table/:tableSlug", getEnter)
|
authorized.GET("/table/:tableSlug", getTable)
|
||||||
authorized.POST("/table/:tableSlug", postEnter)
|
authorized.POST("/table/:tableSlug", postTable)
|
||||||
|
authorized.POST("/table/:tableSlug/reset", postTableReset)
|
||||||
|
|
||||||
authorized.GET("/history", getHistory)
|
authorized.GET("/history", getHistory)
|
||||||
authorized.GET("/leaderboard", getLeaderboard)
|
authorized.GET("/leaderboard", getLeaderboard)
|
||||||
|
|||||||
@@ -67,5 +67,5 @@ type LoginToken struct {
|
|||||||
type Table struct {
|
type Table struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
Name string
|
Name string
|
||||||
Slug string
|
Slug string `gorm:"uniqueIndex;size:128"`
|
||||||
}
|
}
|
||||||
|
|||||||
119
src/routes.go
119
src/routes.go
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@@ -9,11 +10,30 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getEnter(c *gin.Context) {
|
func getEnter(c *gin.Context) {
|
||||||
|
sess := sessions.Default(c)
|
||||||
|
tableSlug := sess.Get("TableSlug")
|
||||||
|
|
||||||
|
var slug string
|
||||||
|
if tableSlug != nil {
|
||||||
|
slug = tableSlug.(string)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if slug != "" {
|
||||||
|
|
||||||
|
var table *Table
|
||||||
|
db.Model(&Table{}).Where("slug = ?", slug).First(&table)
|
||||||
|
|
||||||
|
tm.Render(c, "enter", gin.H{"Table": table})
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
tm.Render(c, "enter", gin.H{})
|
tm.Render(c, "enter", gin.H{})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +44,16 @@ func postEnter(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sess := sessions.Default(c)
|
||||||
|
tableSlug := sess.Get("TableSlug")
|
||||||
|
|
||||||
|
var slug string
|
||||||
|
if tableSlug != nil {
|
||||||
|
slug = tableSlug.(string)
|
||||||
|
|
||||||
|
}
|
||||||
|
sess.Delete("TableSlug")
|
||||||
|
|
||||||
// Parse form
|
// Parse form
|
||||||
a1h := strings.TrimSpace(c.PostForm("a1"))
|
a1h := strings.TrimSpace(c.PostForm("a1"))
|
||||||
a2h := strings.TrimSpace(c.PostForm("a2"))
|
a2h := strings.TrimSpace(c.PostForm("a2"))
|
||||||
@@ -96,9 +126,18 @@ func postEnter(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create game
|
// Create game
|
||||||
g := Game{
|
var g Game
|
||||||
|
if slug != "" {
|
||||||
|
g = Game{
|
||||||
ScoreA: scoreA,
|
ScoreA: scoreA,
|
||||||
ScoreB: scoreB,
|
ScoreB: scoreB,
|
||||||
|
Table: &Table{Slug: slug},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g = Game{
|
||||||
|
ScoreA: scoreA,
|
||||||
|
ScoreB: scoreB,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := db.Create(&g).Error; err != nil {
|
if err := db.Create(&g).Error; err != nil {
|
||||||
c.String(http.StatusInternalServerError, "game create error")
|
c.String(http.StatusInternalServerError, "game create error")
|
||||||
@@ -304,6 +343,10 @@ func postLogin(c *gin.Context) {
|
|||||||
link := strings.TrimRight(baseURL, "/") + "/magic?token=" + token
|
link := strings.TrimRight(baseURL, "/") + "/magic?token=" + token
|
||||||
log.Printf("[MAGIC LINK] %s for %s (valid until %s)\n", link, email, lt.ExpiresAt.Format(time.RFC3339))
|
log.Printf("[MAGIC LINK] %s for %s (valid until %s)\n", link, email, lt.ExpiresAt.Format(time.RFC3339))
|
||||||
|
|
||||||
|
if err := sendEmail(email, link); err != nil {
|
||||||
|
c.Status(500)
|
||||||
|
}
|
||||||
|
|
||||||
tm.Render(c, "sent", gin.H{"Email": email})
|
tm.Render(c, "sent", gin.H{"Email": email})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,3 +378,77 @@ func getMagic(c *gin.Context) {
|
|||||||
|
|
||||||
c.Redirect(302, "/enter")
|
c.Redirect(302, "/enter")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TableInfo struct {
|
||||||
|
Players map[uint]*User
|
||||||
|
PlayerCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
var tables = make(map[string]*TableInfo)
|
||||||
|
|
||||||
|
func getTable(c *gin.Context) {
|
||||||
|
slug := c.Param("tableSlug")
|
||||||
|
u := getSessionUser(c)
|
||||||
|
if tables[slug] == nil {
|
||||||
|
tables[slug] = &TableInfo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
table := tables[slug]
|
||||||
|
if tables[slug].Players == nil {
|
||||||
|
tables[slug].Players = make(map[uint]*User)
|
||||||
|
}
|
||||||
|
|
||||||
|
if table.PlayerCount >= 4 {
|
||||||
|
tm.Render(c, "table_status", gin.H{"Full": "true", "CurrentSlug": slug, "Table": tables[slug]})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Players[u.ID] = u
|
||||||
|
table.PlayerCount = len(table.Players)
|
||||||
|
|
||||||
|
tm.Render(c, "table_status", gin.H{"CurrentSlug": slug, "Table": tables[slug]})
|
||||||
|
}
|
||||||
|
|
||||||
|
func postTable(c *gin.Context) {
|
||||||
|
slug := c.Param("tableSlug")
|
||||||
|
table := tables[slug]
|
||||||
|
session := sessions.Default(c)
|
||||||
|
|
||||||
|
if len(table.Players) == 2 || len(table.Players) == 4 {
|
||||||
|
|
||||||
|
// Pretend we got some values from somewhere else
|
||||||
|
var count int
|
||||||
|
tags := []string{"a1", "b1", "a2", "b2"}
|
||||||
|
formData := make(map[string]string)
|
||||||
|
for _, value := range table.Players {
|
||||||
|
formData[tags[count]] = value.Username
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if b, err := json.Marshal(formData); err == nil {
|
||||||
|
session.Set("form_data", string(b))
|
||||||
|
_ = session.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
session.Set("TableSlug", slug)
|
||||||
|
_ = session.Save()
|
||||||
|
|
||||||
|
// Reset the table
|
||||||
|
tables[slug] = &TableInfo{}
|
||||||
|
|
||||||
|
db.Save(&Table{Slug: slug})
|
||||||
|
|
||||||
|
c.Redirect(302, "/enter")
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
SetMessage(c, "Wrong amount of players!")
|
||||||
|
c.Redirect(302, "/table/"+slug)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func postTableReset(c *gin.Context) {
|
||||||
|
slug := c.Param("tableSlug")
|
||||||
|
tables[slug] = &TableInfo{}
|
||||||
|
SetMessage(c, "Resettet the table!")
|
||||||
|
c.Redirect(302, "/table/"+slug)
|
||||||
|
}
|
||||||
|
|||||||
44
src/utils.go
44
src/utils.go
@@ -1,11 +1,18 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gopkg.in/gomail.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustRandToken(n int) string {
|
func mustRandToken(n int) string {
|
||||||
@@ -134,3 +141,40 @@ func stripAfterDot(s string) string {
|
|||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nameFromEmail(email string) string {
|
||||||
|
parts := strings.SplitN(email, "@", 2)
|
||||||
|
if len(parts) > 0 {
|
||||||
|
return parts[0]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendEmail(email string, token string) error {
|
||||||
|
// Parse the email body template
|
||||||
|
tmpl, err := template.ParseFiles("other/email.txt")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute template with provided data
|
||||||
|
var body bytes.Buffer
|
||||||
|
if err := tmpl.Execute(&body, gin.H{"Token": token, "Name": nameFromEmail(email)}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compose email
|
||||||
|
m := gomail.NewMessage()
|
||||||
|
m.SetHeader("From", os.Getenv("SMTP_MAIL"))
|
||||||
|
m.SetHeader("To", email)
|
||||||
|
m.SetHeader("Subject", "Registration/Login to QRank")
|
||||||
|
m.SetBody("text/plain", body.String())
|
||||||
|
|
||||||
|
// Dial and send
|
||||||
|
d := gomail.NewDialer(os.Getenv("SMTP_HOST"), 587, os.Getenv("SMTP_MAIL"), os.Getenv("SMTP_PASS"))
|
||||||
|
if err := d.DialAndSend(m); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
31
templates/table_status.html
Normal file
31
templates/table_status.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{{define "content"}}
|
||||||
|
<div class="card">
|
||||||
|
<h2>Table "{{.CurrentSlug}}"</h2>
|
||||||
|
|
||||||
|
{{if .Full}}<b>This table is full <br></b>{{end}}
|
||||||
|
|
||||||
|
Currently there are {{.Table.PlayerCount}} Players playing on this table.
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
{{range .Table.Players}} <li>{{.Username}}</li> {{end}}
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<form method="get" action="/table/{{.CurrentSlug}}">
|
||||||
|
<button type="submit">Refresh</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p>The game can be finished when there are 2 or 4 players.</p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<form method="post" action="/table/{{.CurrentSlug}}">
|
||||||
|
<button style="background-color: greenyellow; border-color: gray;" type="submit">FinishGame</button>
|
||||||
|
</form>
|
||||||
|
<form method="post" action="/table/{{.CurrentSlug}}/reset">
|
||||||
|
<button style="background-color: coral; border-color: gray;" type="submit">ResetGame</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
Reference in New Issue
Block a user