mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-08 15:41:20 +02:00
530 lines
20 KiB
Templ
530 lines
20 KiB
Templ
package components
|
|
|
|
import (
|
|
"context"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// CreateTemplateContext creates a new context with user information from Gin's context
|
|
func CreateTemplateContext(c *gin.Context) context.Context {
|
|
ctx := context.Background()
|
|
if userID, exists := c.Get("userID"); exists {
|
|
ctx = context.WithValue(ctx, "userID", userID)
|
|
}
|
|
if username, exists := c.Get("username"); exists {
|
|
ctx = context.WithValue(ctx, "username", username)
|
|
}
|
|
if email, exists := c.Get("email"); exists {
|
|
ctx = context.WithValue(ctx, "email", email)
|
|
}
|
|
if isAdmin, exists := c.Get("isAdmin"); exists {
|
|
ctx = context.WithValue(ctx, "isAdmin", isAdmin)
|
|
}
|
|
return ctx
|
|
}
|
|
|
|
templ Layout(title string) {
|
|
@LayoutWithContext(title, context.Background())
|
|
}
|
|
|
|
templ LayoutWithContext(title string, ctx context.Context) {
|
|
<!DOCTYPE html>
|
|
<html lang="en" class="light h-full">
|
|
<head>
|
|
<meta charset="UTF-8"/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"/>
|
|
<meta name="theme-color" content="#2563eb"/>
|
|
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
|
|
<link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
|
|
<link rel="manifest" href="/static/site.webmanifest">
|
|
<title>{ title } - GoMFT</title>
|
|
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
|
<script src="https://unpkg.com/alpinejs@3.13.5/dist/cdn.min.js" defer></script>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="/static/js/app.js"></script>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
|
|
<link rel="stylesheet" href="/static/css/app.css"/>
|
|
<!-- Notyf Toast Notifications -->
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/notyf@3/notyf.min.css" />
|
|
<script src="https://cdn.jsdelivr.net/npm/notyf@3/notyf.min.js"></script>
|
|
<script>
|
|
// Initialize Notyf and make it available globally
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
window.notyfInstance = new Notyf({
|
|
duration: 3000,
|
|
position: {
|
|
x: 'right',
|
|
y: 'bottom',
|
|
},
|
|
types: [
|
|
{
|
|
type: 'success',
|
|
className: 'notyf__toast--success',
|
|
background: '#10B981',
|
|
icon: {
|
|
className: 'fas fa-check-circle',
|
|
tagName: 'i'
|
|
}
|
|
},
|
|
{
|
|
type: 'error',
|
|
className: 'notyf__toast--error',
|
|
background: '#EF4444',
|
|
icon: {
|
|
className: 'fas fa-exclamation-circle',
|
|
tagName: 'i'
|
|
}
|
|
}
|
|
]
|
|
});
|
|
|
|
});
|
|
</script>
|
|
<script>
|
|
tailwind.config = {
|
|
darkMode: 'class',
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
primary: {
|
|
50: '#f0f9ff',
|
|
100: '#e0f2fe',
|
|
200: '#bae6fd',
|
|
300: '#7dd3fc',
|
|
400: '#38bdf8',
|
|
500: '#0ea5e9',
|
|
600: '#0284c7',
|
|
700: '#0369a1',
|
|
800: '#075985',
|
|
900: '#0c4a6e',
|
|
950: '#082f49',
|
|
},
|
|
secondary: {
|
|
50: '#f8fafc',
|
|
100: '#f1f5f9',
|
|
200: '#e2e8f0',
|
|
300: '#cbd5e1',
|
|
400: '#94a3b8',
|
|
500: '#64748b',
|
|
600: '#475569',
|
|
700: '#334155',
|
|
800: '#1e293b',
|
|
900: '#0f172a',
|
|
950: '#020617',
|
|
},
|
|
},
|
|
fontFamily: {
|
|
sans: ['Inter var', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'sans-serif'],
|
|
},
|
|
boxShadow: {
|
|
'custom': '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
|
'custom-lg': '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
|
|
},
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
<style type="text/tailwindcss">
|
|
@layer components {
|
|
.btn-primary {
|
|
@apply px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-all duration-200 shadow-md hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2;
|
|
}
|
|
.btn-secondary {
|
|
@apply px-4 py-2 bg-secondary-200 text-secondary-800 rounded-lg hover:bg-secondary-300 transition-all duration-200 shadow-sm hover:shadow-md focus:outline-none focus:ring-2 focus:ring-secondary-300 focus:ring-offset-2 dark:bg-secondary-700 dark:text-secondary-100 dark:hover:bg-secondary-600;
|
|
}
|
|
.btn-danger {
|
|
@apply px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-all duration-200 shadow-md hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2;
|
|
}
|
|
.btn-warning {
|
|
@apply px-4 py-2 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 transition-all duration-200 shadow-md hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2;
|
|
}
|
|
.form-input {
|
|
@apply w-full px-3 py-2 border border-secondary-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 shadow-sm transition-all duration-200 dark:bg-secondary-800 dark:border-secondary-700 dark:text-white;
|
|
}
|
|
.form-checkbox {
|
|
@apply rounded border-secondary-300 text-primary-600 shadow-sm focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 dark:border-secondary-700 dark:bg-secondary-800;
|
|
}
|
|
.card {
|
|
@apply bg-white dark:bg-secondary-800 rounded-xl shadow-custom overflow-hidden border border-secondary-200 dark:border-secondary-700 transition-all duration-200 hover:shadow-custom-lg;
|
|
}
|
|
.card-header {
|
|
@apply px-6 py-4 border-b border-secondary-200 dark:border-secondary-700 bg-secondary-50 dark:bg-secondary-900;
|
|
}
|
|
.card-body {
|
|
@apply p-6;
|
|
}
|
|
.badge {
|
|
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium;
|
|
}
|
|
.badge-success {
|
|
@apply bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100;
|
|
}
|
|
.badge-warning {
|
|
@apply bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100;
|
|
}
|
|
.badge-danger {
|
|
@apply bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100;
|
|
}
|
|
.badge-info {
|
|
@apply bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-100;
|
|
}
|
|
.table-container {
|
|
@apply overflow-hidden rounded-lg shadow-sm border border-secondary-200 dark:border-secondary-700;
|
|
}
|
|
.table {
|
|
@apply min-w-full divide-y divide-secondary-200 dark:divide-secondary-700;
|
|
}
|
|
.table thead {
|
|
@apply bg-secondary-50 dark:bg-secondary-900;
|
|
}
|
|
.table th {
|
|
@apply px-6 py-3 text-left text-xs font-medium text-secondary-500 uppercase tracking-wider dark:text-secondary-400;
|
|
}
|
|
.table tbody {
|
|
@apply bg-white dark:bg-secondary-800 divide-y divide-secondary-200 dark:divide-secondary-700;
|
|
}
|
|
.table td {
|
|
@apply px-6 py-4 whitespace-nowrap text-sm text-secondary-900 dark:text-secondary-100;
|
|
}
|
|
.nav-link {
|
|
@apply inline-flex items-center px-1 pt-1 text-secondary-500 hover:text-secondary-700 dark:text-secondary-400 dark:hover:text-secondary-200 border-b-2 border-transparent hover:border-primary-500 transition-all duration-200;
|
|
}
|
|
.nav-link-active {
|
|
@apply inline-flex items-center px-1 pt-1 text-primary-600 dark:text-primary-400 border-b-2 border-primary-500;
|
|
}
|
|
}
|
|
|
|
/* Dark mode styles */
|
|
.dark body {
|
|
@apply bg-secondary-900 text-secondary-100;
|
|
}
|
|
.dark .bg-white {
|
|
@apply bg-secondary-800;
|
|
}
|
|
.dark .text-secondary-800 {
|
|
@apply text-secondary-100;
|
|
}
|
|
.dark .text-secondary-700 {
|
|
@apply text-secondary-200;
|
|
}
|
|
.dark .text-secondary-500 {
|
|
@apply text-secondary-300;
|
|
}
|
|
.dark .hover\:text-secondary-700:hover {
|
|
@apply text-secondary-100;
|
|
}
|
|
.dark .hover\:bg-secondary-100:hover {
|
|
@apply bg-secondary-700;
|
|
}
|
|
.dark .bg-secondary-50 {
|
|
@apply bg-secondary-900;
|
|
}
|
|
.dark .bg-secondary-200 {
|
|
@apply bg-secondary-700;
|
|
}
|
|
.dark .shadow-sm {
|
|
@apply shadow-secondary-900;
|
|
}
|
|
|
|
/* Additional dark mode improvements */
|
|
.dark .bg-secondary-50 {
|
|
@apply bg-secondary-900;
|
|
}
|
|
.dark .bg-secondary-100 {
|
|
@apply bg-secondary-800;
|
|
}
|
|
.dark .border-secondary-200 {
|
|
@apply border-secondary-700;
|
|
}
|
|
|
|
/* Custom animations */
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
.animate-fadeIn {
|
|
animation: fadeIn 0.3s ease-in-out;
|
|
}
|
|
|
|
/* Custom scrollbar */
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
::-webkit-scrollbar-track {
|
|
background: theme('colors.secondary.100');
|
|
border-radius: 4px;
|
|
}
|
|
.dark ::-webkit-scrollbar-track {
|
|
background: theme('colors.secondary.800');
|
|
}
|
|
::-webkit-scrollbar-thumb {
|
|
background: theme('colors.secondary.300');
|
|
border-radius: 4px;
|
|
}
|
|
.dark ::-webkit-scrollbar-thumb {
|
|
background: theme('colors.secondary.600');
|
|
}
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: theme('colors.secondary.400');
|
|
}
|
|
.dark ::-webkit-scrollbar-thumb:hover {
|
|
background: theme('colors.secondary.500');
|
|
}
|
|
</style>
|
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
|
</head>
|
|
<body class="min-h-screen flex flex-col bg-secondary-50 dark:bg-secondary-900 h-full">
|
|
if isLoggedIn(ctx) {
|
|
<nav class="bg-white dark:bg-secondary-800 shadow-sm sticky top-0 z-10">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="flex justify-between h-16">
|
|
<div class="flex">
|
|
<a href="/" class="flex items-center text-xl font-bold text-primary-600 dark:text-primary-400">
|
|
<i class="fas fa-exchange-alt mr-2"></i>
|
|
GoMFT
|
|
</a>
|
|
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
|
<a href="/dashboard" class="nav-link">
|
|
<i class="fas fa-tachometer-alt mr-1"></i> Dashboard
|
|
</a>
|
|
<a href="/configs" class="nav-link">
|
|
<i class="fas fa-cogs mr-1"></i> Configs
|
|
</a>
|
|
<a href="/jobs" class="nav-link">
|
|
<i class="fas fa-tasks mr-1"></i> Jobs
|
|
</a>
|
|
<a href="/history" class="nav-link">
|
|
<i class="fas fa-history mr-1"></i> History
|
|
</a>
|
|
if isAdmin(ctx) {
|
|
<a href="/admin/users" class="nav-link">
|
|
<i class="fas fa-users mr-1"></i> Users
|
|
</a>
|
|
<a href="/admin/tools" class="nav-link">
|
|
<i class="fas fa-tools mr-1"></i> Admin Tools
|
|
</a>
|
|
}
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center space-x-4">
|
|
<!-- Theme Toggle -->
|
|
<button
|
|
id="theme-toggle"
|
|
type="button"
|
|
class="text-secondary-500 dark:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-secondary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 rounded-lg text-sm p-2"
|
|
onclick="toggleTheme()"
|
|
>
|
|
<i class="fas fa-sun hidden dark:block"></i>
|
|
<i class="fas fa-moon block dark:hidden"></i>
|
|
</button>
|
|
|
|
<!-- Mobile menu button -->
|
|
<div class="sm:hidden flex items-center">
|
|
<button
|
|
id="mobile-menu-button"
|
|
class="text-secondary-500 dark:text-secondary-400 hover:bg-secondary-100 dark:hover:bg-secondary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 rounded-lg text-sm p-2"
|
|
x-data="{}"
|
|
@click="$dispatch('toggle-mobile-menu')"
|
|
>
|
|
<i class="fas fa-bars text-lg"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- User Menu -->
|
|
<div x-data="{ open: false }" class="relative">
|
|
<button @click="open = !open" class="flex text-sm rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">
|
|
<span class="sr-only">Open user menu</span>
|
|
<div class="h-8 w-8 rounded-full bg-primary-100 text-primary-700 flex items-center justify-center dark:bg-primary-900 dark:text-primary-300">
|
|
<span class="text-sm font-medium">{ getUserInitial(ctx) }</span>
|
|
</div>
|
|
</button>
|
|
<div x-show="open"
|
|
@click.away="open = false"
|
|
class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-secondary-800 dark:ring-secondary-700 animate-fadeIn"
|
|
role="menu"
|
|
aria-orientation="vertical"
|
|
aria-labelledby="user-menu-button"
|
|
tabindex="-1">
|
|
<div class="px-4 py-2 text-xs text-secondary-500 dark:text-secondary-400 border-b border-secondary-200 dark:border-secondary-700">
|
|
Signed in as <span class="font-medium">{ getUserEmail(ctx) }</span>
|
|
</div>
|
|
<a href="/profile" class="block px-4 py-2 text-sm text-secondary-700 hover:bg-secondary-100 dark:text-secondary-200 dark:hover:bg-secondary-700" role="menuitem">
|
|
<i class="fas fa-user-circle mr-2"></i> Profile
|
|
</a>
|
|
<form method="POST" action="/logout">
|
|
<button type="submit" class="block w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-secondary-100 dark:hover:bg-secondary-700" role="menuitem">
|
|
<i class="fas fa-sign-out-alt mr-2"></i> Sign out
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Mobile Navigation Menu -->
|
|
<div
|
|
x-data="{ open: false }"
|
|
x-on:toggle-mobile-menu.window="open = !open"
|
|
x-show="open"
|
|
x-transition:enter="transition ease-out duration-200"
|
|
x-transition:enter-start="opacity-0 transform -translate-y-2"
|
|
x-transition:enter-end="opacity-100 transform translate-y-0"
|
|
x-transition:leave="transition ease-in duration-150"
|
|
x-transition:leave-start="opacity-100 transform translate-y-0"
|
|
x-transition:leave-end="opacity-0 transform -translate-y-2"
|
|
class="sm:hidden bg-white dark:bg-secondary-800 shadow-md"
|
|
>
|
|
<div class="px-2 pt-2 pb-3 space-y-1">
|
|
<a href="/dashboard" class="block px-3 py-2 rounded-md text-base font-medium text-secondary-700 dark:text-secondary-300 hover:bg-primary-50 dark:hover:bg-secondary-700 hover:text-primary-600 dark:hover:text-primary-400">
|
|
<i class="fas fa-tachometer-alt mr-2"></i> Dashboard
|
|
</a>
|
|
<a href="/configs" class="block px-3 py-2 rounded-md text-base font-medium text-secondary-700 dark:text-secondary-300 hover:bg-primary-50 dark:hover:bg-secondary-700 hover:text-primary-600 dark:hover:text-primary-400">
|
|
<i class="fas fa-cogs mr-2"></i> Configs
|
|
</a>
|
|
<a href="/jobs" class="block px-3 py-2 rounded-md text-base font-medium text-secondary-700 dark:text-secondary-300 hover:bg-primary-50 dark:hover:bg-secondary-700 hover:text-primary-600 dark:hover:text-primary-400">
|
|
<i class="fas fa-tasks mr-2"></i> Jobs
|
|
</a>
|
|
<a href="/history" class="block px-3 py-2 rounded-md text-base font-medium text-secondary-700 dark:text-secondary-300 hover:bg-primary-50 dark:hover:bg-secondary-700 hover:text-primary-600 dark:hover:text-primary-400">
|
|
<i class="fas fa-history mr-2"></i> History
|
|
</a>
|
|
if isAdmin(ctx) {
|
|
<a href="/admin/users" class="block px-3 py-2 rounded-md text-base font-medium text-secondary-700 dark:text-secondary-300 hover:bg-primary-50 dark:hover:bg-secondary-700 hover:text-primary-600 dark:hover:text-primary-400">
|
|
<i class="fas fa-users mr-2"></i> Users
|
|
</a>
|
|
<a href="/admin/tools" class="block px-3 py-2 rounded-md text-base font-medium text-secondary-700 dark:text-secondary-300 hover:bg-primary-50 dark:hover:bg-secondary-700 hover:text-primary-600 dark:hover:text-primary-400">
|
|
<i class="fas fa-tools mr-2"></i> Admin Tools
|
|
</a>
|
|
}
|
|
<a href="/profile" class="block px-3 py-2 rounded-md text-base font-medium text-secondary-700 dark:text-secondary-300 hover:bg-primary-50 dark:hover:bg-secondary-700 hover:text-primary-600 dark:hover:text-primary-400">
|
|
<i class="fas fa-user-circle mr-2"></i> Profile
|
|
</a>
|
|
<form method="POST" action="/logout">
|
|
<button type="submit" class="w-full text-left block px-3 py-2 rounded-md text-base font-medium text-red-600 hover:bg-red-50 dark:hover:bg-red-900 hover:text-red-700 dark:hover:text-red-400">
|
|
<i class="fas fa-sign-out-alt mr-2"></i> Sign out
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
}
|
|
<main class="flex-grow w-full max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8 animate-fadeIn pb-mobile-nav">
|
|
<!-- Hidden notification area for HTMX targets -->
|
|
<div id="notification-area" class="hidden" hx-swap-oob="true"></div>
|
|
|
|
{ children... }
|
|
</main>
|
|
<footer class="bg-white dark:bg-secondary-800 shadow-inner mt-auto w-full">
|
|
<div class="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
|
|
<p class="text-center text-sm text-secondary-500 dark:text-secondary-400">
|
|
GoMFT © { getCurrentYear() } | Secure File Transfer Solution
|
|
</p>
|
|
</div>
|
|
</footer>
|
|
|
|
if isLoggedIn(ctx) {
|
|
<!-- Mobile Bottom Navigation -->
|
|
<div class="sm:hidden fixed bottom-0 left-0 right-0 bg-white dark:bg-secondary-800 border-t border-secondary-200 dark:border-secondary-700 shadow-lg mobile-nav-container z-10">
|
|
<div class="grid grid-cols-5 h-16">
|
|
<a href="/dashboard" class="flex flex-col items-center justify-center text-secondary-500 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400">
|
|
<i class="fas fa-tachometer-alt text-lg"></i>
|
|
<span class="text-xs mt-1">Dashboard</span>
|
|
</a>
|
|
<a href="/configs" class="flex flex-col items-center justify-center text-secondary-500 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400">
|
|
<i class="fas fa-cogs text-lg"></i>
|
|
<span class="text-xs mt-1">Configs</span>
|
|
</a>
|
|
<a href="/jobs" class="flex flex-col items-center justify-center text-secondary-500 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400">
|
|
<i class="fas fa-tasks text-lg"></i>
|
|
<span class="text-xs mt-1">Jobs</span>
|
|
</a>
|
|
<a href="/history" class="flex flex-col items-center justify-center text-secondary-500 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400">
|
|
<i class="fas fa-history text-lg"></i>
|
|
<span class="text-xs mt-1">History</span>
|
|
</a>
|
|
<button
|
|
type="button"
|
|
class="flex flex-col items-center justify-center text-secondary-500 dark:text-secondary-400 hover:text-primary-600 dark:hover:text-primary-400"
|
|
x-data="{}"
|
|
@click="$dispatch('toggle-mobile-menu')"
|
|
>
|
|
<i class="fas fa-ellipsis-h text-lg"></i>
|
|
<span class="text-xs mt-1">More</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
</body>
|
|
</html>
|
|
}
|
|
|
|
// Helper function to check if user is admin
|
|
func isAdmin(ctx context.Context) bool {
|
|
// Try as bool first
|
|
if admin, ok := ctx.Value("isAdmin").(bool); ok {
|
|
return admin
|
|
}
|
|
// Try as interface{} (from JWT claims)
|
|
if admin, ok := ctx.Value("isAdmin").(interface{}); ok {
|
|
if boolVal, ok := admin.(bool); ok {
|
|
return boolVal
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Helper function to check if user is logged in
|
|
func isLoggedIn(ctx context.Context) bool {
|
|
// First try as uint
|
|
if userID, ok := ctx.Value("userID").(uint); ok && userID > 0 {
|
|
return true
|
|
}
|
|
// Then try as float64 (from JWT claims)
|
|
if userID, ok := ctx.Value("userID").(float64); ok && userID > 0 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Helper function to get user initial for avatar
|
|
func getUserInitial(ctx context.Context) string {
|
|
// Try as string first
|
|
if username, ok := ctx.Value("username").(string); ok && username != "" {
|
|
return string(username[0])
|
|
}
|
|
// Try as interface{} (from JWT claims)
|
|
if username, ok := ctx.Value("username").(interface{}); ok {
|
|
if strVal, ok := username.(string); ok && strVal != "" {
|
|
return string(strVal[0])
|
|
}
|
|
}
|
|
// Try email as fallback
|
|
if email, ok := ctx.Value("email").(string); ok && email != "" {
|
|
return string(email[0])
|
|
}
|
|
return "U"
|
|
}
|
|
|
|
// Helper function to get user email
|
|
func getUserEmail(ctx context.Context) string {
|
|
// Try as string first
|
|
if email, ok := ctx.Value("email").(string); ok && email != "" {
|
|
return email
|
|
}
|
|
// Try as interface{} (from JWT claims)
|
|
if email, ok := ctx.Value("email").(interface{}); ok {
|
|
if strVal, ok := email.(string); ok && strVal != "" {
|
|
return strVal
|
|
}
|
|
}
|
|
return "user@example.com"
|
|
}
|
|
|
|
// Helper function to get current year
|
|
func getCurrentYear() string {
|
|
return "2025"
|
|
} |