More layout and customisations.
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/gorilla/csrf"
|
||||
)
|
||||
|
||||
// ToDo this should not be in draw for home!
|
||||
func Home(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
rows, err := db.Query(`
|
||||
@@ -50,8 +51,9 @@ func Home(db *sql.DB) http.HandlerFunc {
|
||||
context := helpers.TemplateContext(w, r)
|
||||
context["Data"] = results
|
||||
|
||||
tmpl := template.Must(template.ParseFiles(
|
||||
tmpl := template.Must(template.New("").Funcs(helpers.TemplateFuncs()).ParseFiles(
|
||||
"templates/layout.html",
|
||||
"templates/topbar.html",
|
||||
"templates/index.html",
|
||||
))
|
||||
|
||||
@@ -69,6 +71,7 @@ func NewDraw(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
tmpl := template.Must(template.ParseFiles(
|
||||
"templates/layout.html",
|
||||
"templates/topbar.html",
|
||||
"templates/new_draw.html",
|
||||
))
|
||||
|
||||
|
||||
@@ -13,6 +13,22 @@ type User struct {
|
||||
IsAdmin bool
|
||||
}
|
||||
|
||||
type Notification struct {
|
||||
ID int
|
||||
Title string
|
||||
Message string
|
||||
IsRead bool
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
ID int
|
||||
Sender string
|
||||
Subject string
|
||||
IsRead bool
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
var db *sql.DB
|
||||
|
||||
func SetDB(database *sql.DB) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import "topbar.css";
|
||||
|
||||
body { font-family: Arial, sans-serif; margin: 0px; }
|
||||
table { border-collapse: collapse; width: 100%; margin-top: 20px; }
|
||||
th, td { padding: 8px 12px; border: 1px solid #ddd; text-align: center; }
|
||||
@@ -15,23 +17,6 @@ th { background-color: #f5f5f5; }
|
||||
}
|
||||
.flash { padding: 10px; color: green; background: #e9ffe9; border: 1px solid #c3e6c3; margin-bottom: 15px; }
|
||||
|
||||
.dropdown-admin-box {
|
||||
min-width: 350px;
|
||||
}
|
||||
|
||||
.dropdown-notification-box {
|
||||
min-width: 350px;
|
||||
}
|
||||
|
||||
.dropdown-message-box {
|
||||
min-width: 350px;
|
||||
}
|
||||
|
||||
.btn-xs {
|
||||
padding: 2px 6px;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Ball Stuff */
|
||||
.ball {
|
||||
|
||||
50
static/css/topbar.css
Normal file
50
static/css/topbar.css
Normal file
@@ -0,0 +1,50 @@
|
||||
.dropdown-admin-box {
|
||||
min-width: 350px;
|
||||
}
|
||||
|
||||
.dropdown-notification-box {
|
||||
min-width: 350px;
|
||||
}
|
||||
|
||||
.dropdown-message-box {
|
||||
min-width: 350px;
|
||||
}
|
||||
|
||||
.dropdown-with-arrow::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: 3.5px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 9px solid transparent;
|
||||
border-right: 9px solid transparent;
|
||||
border-bottom: 9px solid white;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dropdown-with-arrow::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -9px;
|
||||
right: 3.5px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 9px solid transparent;
|
||||
border-right: 9px solid transparent;
|
||||
border-bottom: 9px solid rgba(0, 0, 0, 0.15);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.btn-xs {
|
||||
padding: 2px 6px;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.badge-small {
|
||||
font-size: 0.9rem;
|
||||
padding: 2px 5px;
|
||||
line-height: 1;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
11
storage/badgecounts.go
Normal file
11
storage/badgecounts.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package storage
|
||||
|
||||
// "database/sql"
|
||||
|
||||
// // Get all for count
|
||||
// var count int
|
||||
// db.Get(&count, `SELECT COUNT(*) FROM user_notifications WHERE user_id = ? AND is_read = FALSE`, userID)
|
||||
|
||||
// // Then get the top 15 for display
|
||||
// var notifications []Notification
|
||||
// db.Select(¬ifications, `SELECT * FROM user_notifications WHERE user_id = ? AND is_read = FALSE ORDER BY created_at DESC LIMIT 15`, userID)
|
||||
@@ -135,16 +135,46 @@ func InitDB(filepath string) *sql.DB {
|
||||
|
||||
createUsersTable := `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
is_admin BOOLEAN
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
is_admin BOOLEAN
|
||||
);`
|
||||
|
||||
if _, err := db.Exec(createUsersTable); err != nil {
|
||||
log.Fatal("❌ Failed to create Users table:", err)
|
||||
}
|
||||
|
||||
createUsersMessageTable := `
|
||||
CREATE TABLE IF NOT EXISTS users_messages (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
title TEXT NOT NULL,
|
||||
message TEXT,
|
||||
is_read BOOLEAN DEFAULT FALSE,
|
||||
type VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);`
|
||||
|
||||
if _, err := db.Exec(createUsersMessageTable); err != nil {
|
||||
log.Fatal("❌ Failed to create Users messages table:", err)
|
||||
}
|
||||
|
||||
createUsersNotificationTable := `
|
||||
CREATE TABLE IF NOT EXISTS users_notification (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
sender_name VARCHAR(100),
|
||||
subject TEXT,
|
||||
body TEXT,
|
||||
is_read BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);`
|
||||
|
||||
if _, err := db.Exec(createUsersNotificationTable); err != nil {
|
||||
log.Fatal("❌ Failed to create Users notification table:", err)
|
||||
}
|
||||
|
||||
createAuditLogTable := `
|
||||
CREATE TABLE IF NOT EXISTS auditlog (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
@@ -10,111 +10,8 @@
|
||||
</head>
|
||||
|
||||
<body class="d-flex flex-column min-vh-100">
|
||||
|
||||
<!-- Topbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light px-3">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||
<img src="/static/img/logo.png" alt="Logo" height="30" class="me-2">
|
||||
<span>SynLotto</span>
|
||||
</a>
|
||||
<div class="ms-auto d-flex align-items-center gap-3">
|
||||
{{ if .User }}
|
||||
{{ if .IsAdmin }}
|
||||
<!-- Admin Dropdown -->
|
||||
<div class="dropdown">
|
||||
<a class="nav-link text-dark" href="#" id="adminDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-shield-lock fs-5 position-relative"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end dropdown-admin-box shadow-sm" aria-labelledby="adminDropdown">
|
||||
<li class="dropdown-header text-center fw-bold">Admin Menu</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li class="text-center"><a href="/admin/dashboard" class="dropdown-item">Tools</a></li>
|
||||
<li class="text-center"><a href="/admin/dashboard" class="dropdown-item">Audit Logs</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li class="text-center"><a href="/admin/dashboard" class="dropdown-item">Open Dashboard</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{{ end }}
|
||||
<!-- Notification Dropdown -->
|
||||
<div class="dropdown">
|
||||
<a class="nav-link text-dark" href="#" id="notificationDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-bell fs-5 position-relative">
|
||||
<span class="position-absolute top-0 start-0 translate-middle badge rounded-pill bg-warning text-dark" style="transform: translate(-40%, -50%)">
|
||||
3
|
||||
</span>
|
||||
</i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end dropdown-notification-box shadow-sm" aria-labelledby="notificationDropdown">
|
||||
<li class="dropdown-header text-center fw-bold">Notifications</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
|
||||
<!-- Example notification -->
|
||||
<li class="px-3 py-2">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi bi-info-circle text-primary me-2 fs-4"></i>
|
||||
<div>
|
||||
<div class="fw-semibold">System Update</div>
|
||||
<small class="text-muted">A new lottery draw has been posted.</small>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
|
||||
<li class="px-3 py-2">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi bi-check-circle text-success me-2 fs-4"></i>
|
||||
<div>
|
||||
<div class="fw-semibold">Sync Complete</div>
|
||||
<small class="text-muted">All results are up-to-date.</small>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li class="text-center"><a href="/notifications" class="dropdown-item">View all notifications</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Message Dropdown -->
|
||||
<div class="dropdown">
|
||||
<a class="nav-link text-dark" href="#" id="messageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-envelope fs-5 position-relative">
|
||||
<!-- Unread badge (example: 2 messages) -->
|
||||
<span class="position-absolute top-0 start-0 translate-middle badge rounded-pill bg-danger text-dark" style="transform: translate(-40%, -50%)">
|
||||
2
|
||||
</span>
|
||||
</i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end dropdown-message-box shadow-sm" aria-labelledby="messageDropdown" style="min-width: 300px;">
|
||||
<li class="dropdown-header text-center fw-bold">Messages</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
|
||||
<!-- Example message item -->
|
||||
<li class="px-3 py-2">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi bi-person-circle me-2 fs-4 text-secondary"></i>
|
||||
<div>
|
||||
<div class="fw-semibold">Admin</div>
|
||||
<small class="text-muted">Welcome to SynLotto!</small>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li class="text-center"><a href="/messages" class="dropdown-item">View all messages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- User Greeting -->
|
||||
<span class="navbar-text">Hello, {{ .User.Username }}</span>
|
||||
<a class="btn btn-outline-danger btn-xs" href="/logout">Logout</a>
|
||||
{{ else }}
|
||||
<a class="btn btn-outline-primary btn-sm" href="/login">Login</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{{ template "topbar" . }}
|
||||
<!-- Main Layout -->
|
||||
<div class="container-fluid flex-grow-1">
|
||||
<div class="row">
|
||||
|
||||
100
templates/topbar.html
Normal file
100
templates/topbar.html
Normal file
@@ -0,0 +1,100 @@
|
||||
{{ define "topbar" }}
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light px-3">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||
<img src="/static/img/logo.png" alt="Logo" height="30" class="me-2">
|
||||
<span>SynLotto</span>
|
||||
</a>
|
||||
<div class="ms-auto d-flex align-items-center gap-3">
|
||||
{{ if .User }}
|
||||
{{ if .IsAdmin }}
|
||||
<!-- Admin Dropdown -->
|
||||
<div class="dropdown">
|
||||
<a class="nav-link text-dark" href="#" id="adminDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-shield-lock fs-5 position-relative"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end dropdown-admin-box shadow-sm dropdown-with-arrow" aria-labelledby="adminDropdown">
|
||||
<li class="dropdown-header text-center fw-bold">Admin Menu</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li class="text-center"><a href="/admin/dashboard" class="dropdown-item">Tools</a></li>
|
||||
<li class="text-center"><a href="/admin/dashboard" class="dropdown-item">Audit Logs</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li class="text-center"><a href="/admin/dashboard" class="dropdown-item">Open Dashboard</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{{ end }}
|
||||
<!-- Notification Dropdown
|
||||
<div class="dropdown">
|
||||
<a class="nav-link text-dark" href="#" id="notificationDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-bell fs-5 position-relative">
|
||||
{{ if gt .NotificationCount 0 }}
|
||||
<span class="position-absolute top-0 start-0 translate-middle badge rounded-pill bg-warning text-dark badge-small">
|
||||
{{ if gt .NotificationCount 15 }}15+{{ else }}{{ .NotificationCount }}{{ end }}
|
||||
</span>
|
||||
{{ end }}
|
||||
</i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end dropdown-notification-box shadow-sm dropdown-with-arrow" aria-labelledby="notificationDropdown">
|
||||
<li class="dropdown-header text-center fw-bold">Notifications</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
|
||||
{{ $total := len .Notifications }}
|
||||
{{ range $i, $n := .Notifications }}
|
||||
<li class="px-3 py-2">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi bi-info-circle text-primary me-2 fs-4"></i>
|
||||
<div>
|
||||
<div class="fw-semibold">{{ $n.Title }}</div>
|
||||
<small class="text-muted">{{ $n.Message }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{ if lt (add $i 1) $total }}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<li class="text-center text-muted py-2">No notifications</li>
|
||||
{{ end }}
|
||||
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li class="text-center"><a href="/notifications" class="dropdown-item">View all notifications</a></li>
|
||||
</ul>
|
||||
</div> -->
|
||||
|
||||
<!-- Message Dropdown -->
|
||||
<div class="dropdown">
|
||||
<a class="nav-link text-dark" href="#" id="messageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-envelope fs-5 position-relative">
|
||||
<!-- Unread badge (example: 2 messages) -->
|
||||
<span class="position-absolute top-0 start-0 translate-middle badge rounded-pill bg-danger text-dark badge-small">2</span>
|
||||
</i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end dropdown-message-box shadow-sm dropdown-with-arrow" aria-labelledby="messageDropdown">
|
||||
<li class="dropdown-header text-center fw-bold">Messages</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
|
||||
<!-- Example message item -->
|
||||
<li class="px-3 py-2">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi bi-person-circle me-2 fs-4 text-secondary"></i>
|
||||
<div>
|
||||
<div class="fw-semibold">Admin</div>
|
||||
<small class="text-muted">Welcome to SynLotto!</small>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li class="text-center"><a href="/messages" class="dropdown-item">View all messages</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- User Greeting -->
|
||||
<span class="navbar-text">Hello, {{ .User.Username }}</span>
|
||||
<a class="btn btn-outline-danger btn-xs" href="/logout">Logout</a>
|
||||
{{ else }}
|
||||
<a class="btn btn-outline-primary btn-sm" href="/login">Login</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{{ end }}
|
||||
Reference in New Issue
Block a user