mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
feat(wip): leaderboard ui
This commit is contained in:
parent
dba4da8641
commit
b3fa032bde
@ -9,4 +9,5 @@ const (
|
|||||||
ResetPasswordTemplate = "reset-password.tpl.html"
|
ResetPasswordTemplate = "reset-password.tpl.html"
|
||||||
SettingsTemplate = "settings.tpl.html"
|
SettingsTemplate = "settings.tpl.html"
|
||||||
SummaryTemplate = "summary.tpl.html"
|
SummaryTemplate = "summary.tpl.html"
|
||||||
|
LeaderboardTemplate = "leaderboard.tpl.html"
|
||||||
)
|
)
|
||||||
|
2
main.go
2
main.go
@ -212,6 +212,7 @@ func main() {
|
|||||||
// MVC Handlers
|
// MVC Handlers
|
||||||
summaryHandler := routes.NewSummaryHandler(summaryService, userService)
|
summaryHandler := routes.NewSummaryHandler(summaryService, userService)
|
||||||
settingsHandler := routes.NewSettingsHandler(userService, heartbeatService, summaryService, aliasService, aggregationService, languageMappingService, projectLabelService, keyValueService, mailService)
|
settingsHandler := routes.NewSettingsHandler(userService, heartbeatService, summaryService, aliasService, aggregationService, languageMappingService, projectLabelService, keyValueService, mailService)
|
||||||
|
leaderboardHandler := routes.NewLeaderboardHandler(userService, leaderboardService)
|
||||||
homeHandler := routes.NewHomeHandler(keyValueService)
|
homeHandler := routes.NewHomeHandler(keyValueService)
|
||||||
loginHandler := routes.NewLoginHandler(userService, mailService)
|
loginHandler := routes.NewLoginHandler(userService, mailService)
|
||||||
imprintHandler := routes.NewImprintHandler(keyValueService)
|
imprintHandler := routes.NewImprintHandler(keyValueService)
|
||||||
@ -246,6 +247,7 @@ func main() {
|
|||||||
loginHandler.RegisterRoutes(rootRouter)
|
loginHandler.RegisterRoutes(rootRouter)
|
||||||
imprintHandler.RegisterRoutes(rootRouter)
|
imprintHandler.RegisterRoutes(rootRouter)
|
||||||
summaryHandler.RegisterRoutes(rootRouter)
|
summaryHandler.RegisterRoutes(rootRouter)
|
||||||
|
leaderboardHandler.RegisterRoutes(rootRouter)
|
||||||
settingsHandler.RegisterRoutes(rootRouter)
|
settingsHandler.RegisterRoutes(rootRouter)
|
||||||
relayHandler.RegisterRoutes(rootRouter)
|
relayHandler.RegisterRoutes(rootRouter)
|
||||||
|
|
||||||
|
22
models/view/leaderboard.go
Normal file
22
models/view/leaderboard.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import "github.com/muety/wakapi/models"
|
||||||
|
|
||||||
|
type LeaderboardViewModel struct {
|
||||||
|
User *models.User
|
||||||
|
Items []*models.LeaderboardItem
|
||||||
|
ItemsByLanguage []*models.LeaderboardItem
|
||||||
|
ApiKey string
|
||||||
|
Success string
|
||||||
|
Error string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LeaderboardViewModel) WithSuccess(m string) *LeaderboardViewModel {
|
||||||
|
s.Success = m
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LeaderboardViewModel) WithError(m string) *LeaderboardViewModel {
|
||||||
|
s.Error = m
|
||||||
|
return s
|
||||||
|
}
|
74
routes/leaderboard.go
Normal file
74
routes/leaderboard.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
conf "github.com/muety/wakapi/config"
|
||||||
|
"github.com/muety/wakapi/middlewares"
|
||||||
|
"github.com/muety/wakapi/models"
|
||||||
|
"github.com/muety/wakapi/models/view"
|
||||||
|
"github.com/muety/wakapi/services"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LeaderboardHandler struct {
|
||||||
|
config *conf.Config
|
||||||
|
userService services.IUserService
|
||||||
|
leaderboardService services.ILeaderboardService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLeaderboardHandler(userService services.IUserService, leaderboardService services.ILeaderboardService) *LeaderboardHandler {
|
||||||
|
return &LeaderboardHandler{
|
||||||
|
config: conf.Get(),
|
||||||
|
userService: userService,
|
||||||
|
leaderboardService: leaderboardService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *LeaderboardHandler) RegisterRoutes(router *mux.Router) {
|
||||||
|
r := router.PathPrefix("/leaderboard").Subrouter()
|
||||||
|
r.Use(
|
||||||
|
middlewares.NewAuthenticateMiddleware(h.userService).
|
||||||
|
WithRedirectTarget(defaultErrorRedirectTarget()).
|
||||||
|
WithOptionalFor([]string{"/"}).
|
||||||
|
Handler,
|
||||||
|
)
|
||||||
|
r.Methods(http.MethodGet).HandlerFunc(h.GetIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *LeaderboardHandler) GetIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if h.config.IsDev() {
|
||||||
|
loadTemplates()
|
||||||
|
}
|
||||||
|
templates[conf.LeaderboardTemplate].Execute(w, h.buildViewModel(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *LeaderboardHandler) buildViewModel(r *http.Request) *view.LeaderboardViewModel {
|
||||||
|
user := middlewares.GetPrincipal(r)
|
||||||
|
|
||||||
|
itemsGeneral, err := h.leaderboardService.GetByInterval(models.IntervalPast7Days)
|
||||||
|
if err != nil {
|
||||||
|
conf.Log().Request(r).Error("error while fetching general leaderboard items - %v", err)
|
||||||
|
return &view.LeaderboardViewModel{Error: criticalError}
|
||||||
|
}
|
||||||
|
|
||||||
|
by := models.SummaryLanguage
|
||||||
|
itemsByLanguage, err := h.leaderboardService.GetAggregatedByInterval(models.IntervalPast7Days, &by)
|
||||||
|
if err != nil {
|
||||||
|
conf.Log().Request(r).Error("error while fetching general leaderboard items - %v", err)
|
||||||
|
return &view.LeaderboardViewModel{Error: criticalError}
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiKey string
|
||||||
|
if user != nil {
|
||||||
|
apiKey = user.ApiKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return &view.LeaderboardViewModel{
|
||||||
|
User: user,
|
||||||
|
Items: itemsGeneral,
|
||||||
|
ItemsByLanguage: itemsByLanguage,
|
||||||
|
ApiKey: apiKey,
|
||||||
|
Success: r.URL.Query().Get("success"),
|
||||||
|
Error: r.URL.Query().Get("error"),
|
||||||
|
}
|
||||||
|
}
|
14
static/assets/js/components/leaderboard.js
Normal file
14
static/assets/js/components/leaderboard.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
PetiteVue.createApp({
|
||||||
|
//$delimiters: ['${', '}'], // https://github.com/vuejs/petite-vue/pull/100
|
||||||
|
activeTab: defaultTab,
|
||||||
|
isActive(tab) {
|
||||||
|
return this.activeTab === tab
|
||||||
|
},
|
||||||
|
updateTab() {
|
||||||
|
this.activeTab = window.location.hash.slice(1) || defaultTab
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateTab()
|
||||||
|
window.addEventListener('hashchange', () => this.updateTab())
|
||||||
|
}
|
||||||
|
}).mount('#leaderboard-page')
|
@ -9,12 +9,7 @@
|
|||||||
|
|
||||||
{{ template "alerts.tpl.html" . }}
|
{{ template "alerts.tpl.html" . }}
|
||||||
|
|
||||||
<div class="absolute flex top-0 right-0 mr-4 mt-10 py-2">
|
{{ template "login-btn.tpl.html" . }}
|
||||||
<div class="mx-1">
|
|
||||||
<a href="login" class="btn-primary">
|
|
||||||
<span class="iconify inline" data-icon="fluent:key-24-filled"></span> Login</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<main class="mt-10 px-4 md:px-10 lg:px-24 flex-grow flex justify-center w-full">
|
<main class="mt-10 px-4 md:px-10 lg:px-24 flex-grow flex justify-center w-full">
|
||||||
<div class="flex flex-col text-white">
|
<div class="flex flex-col text-white">
|
||||||
|
58
views/leaderboard.tpl.html
Normal file
58
views/leaderboard.tpl.html
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
{{ template "head.tpl.html" . }}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const defaultTab = 'total'
|
||||||
|
</script>
|
||||||
|
<script type="module" src="assets/js/components/leaderboard.js"></script>
|
||||||
|
|
||||||
|
<body class="relative bg-gray-900 text-gray-700 p-4 pt-10 flex flex-col min-h-screen {{ if .User }} max-w-screen-xl {{ else }} max-w-screen-lg {{end}} mx-auto justify-center">
|
||||||
|
|
||||||
|
{{ template "alerts.tpl.html" . }}
|
||||||
|
|
||||||
|
{{ if .User }}
|
||||||
|
{{ template "menu-main.tpl.html" . }}
|
||||||
|
{{ else }}
|
||||||
|
{{ template "header.tpl.html" . }}
|
||||||
|
{{ template "login-btn.tpl.html" . }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
<main class="mt-10 flex-grow flex justify-center w-full" v-scope @vue:mounted="mounted" id="leaderboard-page">
|
||||||
|
<div class="flex flex-col flex-grow mt-10">
|
||||||
|
<h1 class="font-semibold text-3xl text-white m-0 mb-4">Leaderboard</h1>
|
||||||
|
|
||||||
|
<ul class="flex space-x-4 mb-16 text-gray-600">
|
||||||
|
<li class="font-semibold text-2xl" v-bind:class="{ 'text-gray-300': isActive('total'), 'hover:text-gray-500': !isActive('total') }">
|
||||||
|
<a href="leaderboard#total" @click="updateTab">Total</a>
|
||||||
|
</li>
|
||||||
|
<li class="font-semibold text-2xl" v-bind:class="{ 'text-gray-300': isActive('language'), 'hover:text-gray-500': !isActive('language') }">
|
||||||
|
<a href="leaderboard#language" @click="updateTab">By Language</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div v-cloak id="total" class="tab flex flex-col space-y-4" v-if="isActive('total')">
|
||||||
|
<ol>
|
||||||
|
{{ range $i, $item := .Items }}
|
||||||
|
<li>{{ $item.Rank }} - {{ $item.UserID }} - {{ $item.Total | duration }}</li>
|
||||||
|
{{ end }}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-cloak id="language" class="tab flex flex-col space-y-4" v-if="isActive('language')">
|
||||||
|
<ol>
|
||||||
|
{{ range $i, $item := .ItemsByLanguage }}
|
||||||
|
<li>{{ $item.Rank }} - {{ $item.UserID }} - {{ $item.Total | duration }}</li>
|
||||||
|
{{ end }}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{{ template "footer.tpl.html" . }}
|
||||||
|
|
||||||
|
{{ template "foot.tpl.html" . }}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
10
views/login-btn.tpl.html
Normal file
10
views/login-btn.tpl.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<div class="absolute flex top-0 right-0 mr-4 mt-10 py-2">
|
||||||
|
<div class="mx-1">
|
||||||
|
<a href="leaderboard" class="btn-default">
|
||||||
|
<span class="iconify inline" data-icon="fluent:data-bar-horizontal-24-filled"></span> Leaderboard</a>
|
||||||
|
</div>
|
||||||
|
<div class="mx-1">
|
||||||
|
<a href="login" class="btn-primary">
|
||||||
|
<span class="iconify inline" data-icon="fluent:key-24-filled"></span> Login</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -10,6 +10,11 @@
|
|||||||
<span class="text-gray-300 hidden lg:inline-block">Dashboard</span>
|
<span class="text-gray-300 hidden lg:inline-block">Dashboard</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a class="menu-item" href="leaderboard">
|
||||||
|
<span class="iconify inline text-2xl text-gray-400" data-icon="fluent:data-bar-horizontal-24-filled"></span>
|
||||||
|
<span class="text-gray-300 hidden lg:inline-block">Leaderboard</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
<div class="menu-item hidden sm:flex imp:cursor-not-allowed">
|
<div class="menu-item hidden sm:flex imp:cursor-not-allowed">
|
||||||
<span class="iconify inline text-2xl text-gray-700" data-icon="bi:people-fill"></span>
|
<span class="iconify inline text-2xl text-gray-700" data-icon="bi:people-fill"></span>
|
||||||
<a class="text-gray-600 leading-none hidden lg:inline-block">Team<br>
|
<a class="text-gray-600 leading-none hidden lg:inline-block">Team<br>
|
||||||
@ -17,13 +22,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="menu-item hidden sm:flex imp:cursor-not-allowed">
|
|
||||||
<span class="iconify inline text-2xl text-gray-700" data-icon="fluent:data-bar-horizontal-24-filled"></span>
|
|
||||||
<a class="text-gray-600 leading-none hidden lg:inline-block">Leaderboard<br>
|
|
||||||
<span class="text-xxs whitespace-nowrap">(coming soon)</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="menu-item relative" @click="state.showDropdownResources = !state.showDropdownResources" data-trigger-for="showDropdownResources">
|
<div class="menu-item relative" @click="state.showDropdownResources = !state.showDropdownResources" data-trigger-for="showDropdownResources">
|
||||||
<span class="iconify inline text-2xl text-gray-400" data-icon="ph:books-bold"></span>
|
<span class="iconify inline text-2xl text-gray-400" data-icon="ph:books-bold"></span>
|
||||||
<a class="text-gray-400 hidden lg:inline-block">Resources</a>
|
<a class="text-gray-400 hidden lg:inline-block">Resources</a>
|
||||||
|
Loading…
Reference in New Issue
Block a user