Under construction page and dynamic profile loading
This commit is contained in:
24
frontend/assets/css/under-construction.css
Normal file
24
frontend/assets/css/under-construction.css
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
.construction-icon-wrapper {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background: rgba(59, 130, 246, 0.1); /* Nutzt die var(--accent) Transparenz */
|
||||||
|
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gear-icon {
|
||||||
|
animation: gearRotate 8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gearRotate {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
if (document.getElementById('items-table-body')) loadItems();
|
if (document.getElementById('items-table-body')) loadItems();
|
||||||
if (document.getElementById('locations-table-body')) loadLocations();
|
if (document.getElementById('locations-table-body')) loadLocations();
|
||||||
if (document.getElementById('projects-table-body')) loadProjects();
|
if (document.getElementById('projects-table-body')) loadProjects();
|
||||||
|
|
||||||
|
loadProfile();
|
||||||
});
|
});
|
||||||
|
|
||||||
function closeModal(id) {
|
function closeModal(id) {
|
||||||
@@ -465,3 +467,18 @@ async function openDashboardModal(url, title, dataKey) {
|
|||||||
tbody.innerHTML = '<tr><td colspan="2" style="color:var(--error); text-align:center; padding:1.5rem;">Failed to load data.</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="2" style="color:var(--error); text-align:center; padding:1.5rem;">Failed to load data.</td></tr>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- PROFILE ----
|
||||||
|
async function loadProfile() {
|
||||||
|
const avatar = document.getElementById("avatar");
|
||||||
|
const username = document.getElementById('username');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await apiRequest('/api/profile');
|
||||||
|
|
||||||
|
avatar.innerText = data.username[0].toLocaleUpperCase();
|
||||||
|
username.innerText = data.username;
|
||||||
|
} catch (e) {
|
||||||
|
username.innerHTML = '<tr><td colspan="2" style="color:var(--error); text-align:center; padding:1.5rem;">Failed to load data.</td></tr>';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,8 +30,8 @@
|
|||||||
|
|
||||||
<div class="profile-dropdown">
|
<div class="profile-dropdown">
|
||||||
<button class="profile-trigger" id="profile-btn">
|
<button class="profile-trigger" id="profile-btn">
|
||||||
<div class="avatar">M</div>
|
<div id="avatar" class="avatar">M</div>
|
||||||
<span class="username">Admin</span>
|
<span id="username" class="username">Loading...</span>
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu" id="dropdown-menu">
|
<div class="dropdown-menu" id="dropdown-menu">
|
||||||
|
|||||||
39
frontend/htmx/under-construction.html
Normal file
39
frontend/htmx/under-construction.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Feature Under Development | MiauInv</title>
|
||||||
|
<link rel="stylesheet" href="/assets/css/theme.css">
|
||||||
|
<link rel="stylesheet" href="/assets/css/under-construction.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="construction-icon-wrapper">
|
||||||
|
<svg class="gear-icon" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="3"></circle>
|
||||||
|
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>Coming Soon</h1>
|
||||||
|
<div class="subtitle">Feature Under Development</div>
|
||||||
|
|
||||||
|
<div class="message success" style="display: block; margin-bottom: 2rem;">
|
||||||
|
<strong>Development in progress!</strong><br>
|
||||||
|
Our team is actively working on this feature to bring you the best experience possible.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="/dashboard" class="btn btn-secondary">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 8px;"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>
|
||||||
|
Back to Dashboard
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="footer-text">
|
||||||
|
Need urgent assistance? <a href="mailto:maurice@miaurizius.de">Contact Developer</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -242,6 +243,36 @@ func UserInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
query := r.URL.Query()
|
query := r.URL.Query()
|
||||||
idParam := query.Get("id")
|
idParam := query.Get("id")
|
||||||
|
|
||||||
|
if idParam == "" {
|
||||||
|
tokenStr := ""
|
||||||
|
authHeader := r.Header.Get("Authorization")
|
||||||
|
|
||||||
|
if strings.HasPrefix(authHeader, "Bearer ") {
|
||||||
|
tokenStr = strings.TrimPrefix(authHeader, "Bearer ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenStr == "" {
|
||||||
|
cookie, err := r.Cookie("access_token")
|
||||||
|
if err == nil {
|
||||||
|
tokenStr = cookie.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenStr == "" {
|
||||||
|
http.Error(w, "Missing token", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := auth.ValidateJWT(tokenStr, models.JWTSecret)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("GET [api/userinfo] " + r.RemoteAddr + ": " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
idParam = claims.UserID
|
||||||
|
}
|
||||||
|
|
||||||
user, err := storage.GetUserById(idParam)
|
user, err := storage.GetUserById(idParam)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("GET [api/userinfo] " + r.RemoteAddr + ": User " + idParam + " not found")
|
log.Println("GET [api/userinfo] " + r.RemoteAddr + ": User " + idParam + " not found")
|
||||||
@@ -251,7 +282,7 @@ func UserInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
err = json.NewEncoder(w).Encode(map[string]interface{}{
|
err = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
"id": user.ID,
|
"id": user.ID,
|
||||||
"name": user.Username,
|
"username": user.Username,
|
||||||
"avatar_url": "",
|
"avatar_url": "",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
|
var JWTSecret []byte
|
||||||
|
|
||||||
// Roles
|
// Roles
|
||||||
const (
|
const (
|
||||||
RoleUser = "user"
|
RoleUser = "user"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"MiauInv/config"
|
"MiauInv/config"
|
||||||
"MiauInv/frontend"
|
"MiauInv/frontend"
|
||||||
"MiauInv/handlers"
|
"MiauInv/handlers"
|
||||||
|
"MiauInv/models"
|
||||||
utils "MiauInv/util"
|
utils "MiauInv/util"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -44,6 +45,8 @@ func InitServer() *Server {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
models.JWTSecret = []byte(jwtSecret)
|
||||||
|
|
||||||
return &Server{
|
return &Server{
|
||||||
Port: cfg.Port,
|
Port: cfg.Port,
|
||||||
JWTSecret: []byte(jwtSecret),
|
JWTSecret: []byte(jwtSecret),
|
||||||
@@ -68,6 +71,7 @@ func (this *Server) Run() {
|
|||||||
mux.Handle("/items", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(frontend.Items)))
|
mux.Handle("/items", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(frontend.Items)))
|
||||||
mux.Handle("/locations", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(frontend.Locations)))
|
mux.Handle("/locations", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(frontend.Locations)))
|
||||||
mux.Handle("/projects", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(frontend.Projects)))
|
mux.Handle("/projects", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(frontend.Projects)))
|
||||||
|
mux.HandleFunc("/profile/", utils.RenderFile("frontend/htmx/under-construction.html"))
|
||||||
if this.AllowRegistration {
|
if this.AllowRegistration {
|
||||||
mux.HandleFunc("/register", utils.RenderFile("frontend/htmx/register.html"))
|
mux.HandleFunc("/register", utils.RenderFile("frontend/htmx/register.html"))
|
||||||
} else {
|
} else {
|
||||||
@@ -80,6 +84,7 @@ func (this *Server) Run() {
|
|||||||
mux.HandleFunc("/api/login", handlers.APILogin)
|
mux.HandleFunc("/api/login", handlers.APILogin)
|
||||||
mux.HandleFunc("/api/refresh", handlers.RefreshToken)
|
mux.HandleFunc("/api/refresh", handlers.RefreshToken)
|
||||||
mux.Handle("/api/logout", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(handlers.Logout)))
|
mux.Handle("/api/logout", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(handlers.Logout)))
|
||||||
|
mux.Handle("/api/profile", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(handlers.UserInfo)))
|
||||||
if this.AllowRegistration {
|
if this.AllowRegistration {
|
||||||
mux.HandleFunc("/api/register", handlers.APIRegister)
|
mux.HandleFunc("/api/register", handlers.APIRegister)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user