aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-03-07 17:15:38 +0530
committerBobby <[email protected]>2026-03-07 17:15:38 +0530
commit57df54999e778887e66775481dab46191b46d0b6 (patch)
tree2009abf151fd34923c7a3ae760d08c3c6bbe72e0
parent6dd57549df7b6679a1aa9888f4d59edaaec5b3f9 (diff)
downloaddove-57df54999e778887e66775481dab46191b46d0b6.tar.xz
dove-57df54999e778887e66775481dab46191b46d0b6.zip
Refactor dashboard templates and enhance URL handling
- Added `variableName` to `urlNode` struct for improved variable handling in URL tags. - Updated `url` function to support variable assignment using the `as` keyword. - Removed deprecated `dashboard.django` template and replaced it with a new layout structure. - Introduced partial templates for header and sidebar to streamline dashboard layout. - Created new HTMX templates for mailboxes, mailbox, overview, and users to enhance dynamic content loading. - Implemented navigation JavaScript to manage active link states in the sidebar. - Refactored render functions to resolve template paths based on HTMX requests. - Updated constants for request key naming consistency.
-rw-r--r--messages/tags.go13
-rw-r--r--pages/dashboard.go4
-rw-r--r--static/css/tailwind.css87
-rw-r--r--static/js/navigation.js10
-rw-r--r--tags/types.go5
-rw-r--r--tags/url.go21
-rw-r--r--templates/dashboard.django30
-rw-r--r--templates/dashboard/htmx/mailbox.htmx.django17
-rw-r--r--templates/dashboard/htmx/mailboxes.htmx.django18
-rw-r--r--templates/dashboard/htmx/overview.htmx.django58
-rw-r--r--templates/dashboard/htmx/users.htmx.django29
-rw-r--r--templates/dashboard/mailbox.django16
-rw-r--r--templates/dashboard/mailboxes.django5
-rw-r--r--templates/dashboard/overview.django5
-rw-r--r--templates/dashboard/users.django5
-rw-r--r--templates/layouts/base.django4
-rw-r--r--templates/layouts/dashboard.django58
-rw-r--r--templates/partials/header.django4
-rw-r--r--templates/partials/sidebar.django49
-rw-r--r--utils/meta/constants.go2
-rw-r--r--utils/shortcuts/functions.go13
-rw-r--r--utils/shortcuts/render.go2
22 files changed, 357 insertions, 98 deletions
diff --git a/messages/tags.go b/messages/tags.go
index 6d23e71..b54090a 100644
--- a/messages/tags.go
+++ b/messages/tags.go
@@ -1,10 +1,11 @@
package messages
const (
- TagExpectedEquals = "Expected '=' after parameter key."
- TagExpectedParamKey = "Expected parameter key identifier."
- TagExpectedRouteName = "Expected route name string."
- TagRegistrationFailed = "Failed to register tag: %s."
- TagRouteNotFound = "Route not found: %s."
- TagTemplateWriteFailed = "Failed to write template output."
+ TagExpectedEquals = "Expected '=' after parameter key."
+ TagExpectedParamKey = "Expected parameter key identifier."
+ TagExpectedRouteName = "Expected route name string."
+ TagExpectedVariableName = "Expected variable name after 'as'."
+ TagRegistrationFailed = "Failed to register tag: %s."
+ TagRouteNotFound = "Route not found: %s."
+ TagTemplateWriteFailed = "Failed to write template output."
)
diff --git a/pages/dashboard.go b/pages/dashboard.go
index 2fd1f1d..0640192 100644
--- a/pages/dashboard.go
+++ b/pages/dashboard.go
@@ -8,6 +8,6 @@ import (
)
func Dashboard(context *fiber.Ctx) error {
- meta.SetPageTitle(context, "Dashboard")
- return shortcuts.Render(context, "dashboard", nil)
+ meta.SetPageTitle(context, "Overview")
+ return shortcuts.Render(context, "dashboard/overview", nil)
}
diff --git a/static/css/tailwind.css b/static/css/tailwind.css
index 2983d29..477fdcd 100644
--- a/static/css/tailwind.css
+++ b/static/css/tailwind.css
@@ -111,6 +111,93 @@
}
}
+.loading-bar {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 3px;
+ z-index: 9999;
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.2s ease;
+}
+
+.loading-bar.htmx-request {
+ opacity: 1;
+}
+
+.loading-bar::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 0%;
+ height: 100%;
+ background: linear-gradient(
+ 90deg,
+ var(--color-accent-500),
+ var(--color-accent-400)
+ );
+}
+
+.loading-bar.htmx-request::before {
+ animation: loading-fill 8s cubic-bezier(0.1, 0.4, 0.2, 0.9) forwards;
+}
+
+.loading-bar::after {
+ content: "";
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 80px;
+ height: 100%;
+ background: linear-gradient(
+ 90deg,
+ transparent,
+ rgba(129, 140, 248, 0.6)
+ );
+ box-shadow: 0 0 12px rgba(129, 140, 248, 0.4);
+ opacity: 0;
+}
+
+.loading-bar.htmx-request::after {
+ animation: loading-fill 8s cubic-bezier(0.1, 0.4, 0.2, 0.9) forwards;
+ opacity: 1;
+}
+
+@keyframes loading-fill {
+ 0% {
+ width: 0%;
+ }
+ 20% {
+ width: 40%;
+ }
+ 50% {
+ width: 65%;
+ }
+ 80% {
+ width: 85%;
+ }
+ 100% {
+ width: 98%;
+ }
+}
+
+.nav-link {
+ color: #a1a1aa;
+}
+
+.nav-link:hover {
+ color: #e4e4e7;
+ background: rgba(255, 255, 255, 0.04);
+}
+
+.nav-link.active {
+ color: #f4f4f5;
+ background: rgba(255, 255, 255, 0.06);
+}
+
::selection {
background-color: var(--color-accent-500);
color: white;
diff --git a/static/js/navigation.js b/static/js/navigation.js
new file mode 100644
index 0000000..b2d293c
--- /dev/null
+++ b/static/js/navigation.js
@@ -0,0 +1,10 @@
+document.body.addEventListener("htmx:afterSwap", function () {
+ var currentPath = window.location.pathname;
+ document.querySelectorAll("#sidebar-nav .nav-link").forEach(function (link) {
+ if (link.getAttribute("href") === currentPath) {
+ link.classList.add("active");
+ } else {
+ link.classList.remove("active");
+ }
+ });
+}); \ No newline at end of file
diff --git a/tags/types.go b/tags/types.go
index b031904..144e05a 100644
--- a/tags/types.go
+++ b/tags/types.go
@@ -12,6 +12,7 @@ type templateTag struct {
}
type urlNode struct {
- routeName string
- params collections.Record[pongo2.IEvaluator]
+ routeName string
+ params collections.Record[pongo2.IEvaluator]
+ variableName string
}
diff --git a/tags/url.go b/tags/url.go
index 3b899c2..0085679 100644
--- a/tags/url.go
+++ b/tags/url.go
@@ -20,7 +20,18 @@ func url(document *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser)
params := make(collections.Record[pongo2.IEvaluator])
+ var variableName string
+
for arguments.Remaining() > 0 {
+ if arguments.Match(pongo2.TokenKeyword, "as") != nil {
+ nameToken := arguments.MatchType(pongo2.TokenIdentifier)
+ if nameToken == nil {
+ return nil, arguments.Error(messages.TagExpectedVariableName, nil)
+ }
+ variableName = nameToken.Val
+ break
+ }
+
keyToken := arguments.MatchType(pongo2.TokenIdentifier)
if keyToken == nil {
return nil, arguments.Error(messages.TagExpectedParamKey, nil)
@@ -39,8 +50,9 @@ func url(document *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser)
}
return &urlNode{
- routeName: routeNameToken.Val,
- params: params,
+ routeName: routeNameToken.Val,
+ params: params,
+ variableName: variableName,
}, nil
}
@@ -64,6 +76,11 @@ func (self *urlNode) Execute(executionContext *pongo2.ExecutionContext, writer p
path = strings.ReplaceAll(path, placeholder, replacement)
}
+ if self.variableName != "" {
+ executionContext.Public[self.variableName] = path
+ return nil
+ }
+
_, writeError := writer.WriteString(path)
if writeError != nil {
return &pongo2.Error{
diff --git a/templates/dashboard.django b/templates/dashboard.django
deleted file mode 100644
index 35c1246..0000000
--- a/templates/dashboard.django
+++ /dev/null
@@ -1,30 +0,0 @@
-{% extends "layouts/base.django" %}
-
-{% block content %}
-<div class="min-h-screen bg-gray-950">
- <nav class="border-b border-gray-800 bg-gray-900/50 backdrop-blur-sm">
- <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
- <div class="flex h-14 items-center justify-between">
- <div class="flex items-center space-x-3">
- <span class="text-lg font-semibold text-white">Dove</span>
- </div>
- {% if AuthEnabled %}
- <div class="flex items-center space-x-4">
- <a href="/auth/logout" class="text-sm text-gray-400 hover:text-white transition">Logout</a>
- </div>
- {% endif %}
- </div>
- </div>
- </nav>
-
- <main class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
- <div class="flex items-center justify-between">
- <h1 class="text-2xl font-bold text-white">Mailboxes</h1>
- </div>
-
- <div class="mt-6 rounded-lg border border-gray-800 bg-gray-900/50 p-12 text-center">
- <p class="text-gray-400">No mailboxes yet. Emails will appear here when received.</p>
- </div>
- </main>
-</div>
-{% endblock %}
diff --git a/templates/dashboard/htmx/mailbox.htmx.django b/templates/dashboard/htmx/mailbox.htmx.django
new file mode 100644
index 0000000..831a07d
--- /dev/null
+++ b/templates/dashboard/htmx/mailbox.htmx.django
@@ -0,0 +1,17 @@
+<h1 id="page-title" class="text-sm font-medium text-zinc-100" hx-swap-oob="true">{{ PageTitle }}</h1>
+<div class="slide-up space-y-6">
+ <div class="glass rounded-xl glow-border">
+ <div class="flex items-center justify-between px-5 py-4 border-b border-white/[0.04]">
+ <h2 class="text-sm font-medium text-zinc-200">Inbox</h2>
+ </div>
+ <div class="flex flex-col items-center justify-center py-16 text-center">
+ <div class="flex items-center justify-center w-12 h-12 rounded-2xl bg-surface-800 mb-4">
+ <svg class="w-6 h-6 text-zinc-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" />
+ </svg>
+ </div>
+ <p class="text-sm text-zinc-400">No emails in this mailbox</p>
+ <p class="mt-1 text-xs text-zinc-600">Emails sent to {{ Address }} will appear here</p>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/templates/dashboard/htmx/mailboxes.htmx.django b/templates/dashboard/htmx/mailboxes.htmx.django
new file mode 100644
index 0000000..57fdf04
--- /dev/null
+++ b/templates/dashboard/htmx/mailboxes.htmx.django
@@ -0,0 +1,18 @@
+<h1 id="page-title" class="text-sm font-medium text-zinc-100" hx-swap-oob="true">{{ PageTitle }}</h1>
+<div class="slide-up space-y-6">
+ <div class="glass rounded-xl glow-border">
+ <div class="flex items-center justify-between px-5 py-4 border-b border-white/[0.04]">
+ <h2 class="text-sm font-medium text-zinc-200">All Mailboxes</h2>
+ <span class="text-xs text-zinc-600">Mailboxes are created automatically when emails are received</span>
+ </div>
+ <div class="flex flex-col items-center justify-center py-16 text-center">
+ <div class="flex items-center justify-center w-12 h-12 rounded-2xl bg-surface-800 mb-4">
+ <svg class="w-6 h-6 text-zinc-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 13.5h3.86a2.25 2.25 0 0 1 2.012 1.244l.256.512a2.25 2.25 0 0 0 2.013 1.244h2.21a2.25 2.25 0 0 0 2.013-1.244l.256-.512a2.25 2.25 0 0 1 2.013-1.244h3.859m-19.5.338V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18v-4.162c0-.224-.034-.447-.1-.661L19.24 5.338a2.25 2.25 0 0 0-2.15-1.588H6.911a2.25 2.25 0 0 0-2.15 1.588L2.35 13.177a2.25 2.25 0 0 0-.1.661Z" />
+ </svg>
+ </div>
+ <p class="text-sm text-zinc-400">No mailboxes yet</p>
+ <p class="mt-1 text-xs text-zinc-600">Send an email to start receiving mail</p>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/templates/dashboard/htmx/overview.htmx.django b/templates/dashboard/htmx/overview.htmx.django
new file mode 100644
index 0000000..8d9e88a
--- /dev/null
+++ b/templates/dashboard/htmx/overview.htmx.django
@@ -0,0 +1,58 @@
+<h1 id="page-title" class="text-sm font-medium text-zinc-100" hx-swap-oob="true">{{ PageTitle }}</h1>
+<div class="slide-up space-y-8">
+ <div class="grid grid-cols-3 gap-5">
+ <div class="glass rounded-xl p-5 glow-border">
+ <div class="flex items-center gap-3 mb-4">
+ <div class="flex items-center justify-center w-9 h-9 rounded-lg bg-accent-500/10">
+ <svg class="w-4.5 h-4.5 text-accent-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 13.5h3.86a2.25 2.25 0 0 1 2.012 1.244l.256.512a2.25 2.25 0 0 0 2.013 1.244h2.21a2.25 2.25 0 0 0 2.013-1.244l.256-.512a2.25 2.25 0 0 1 2.013-1.244h3.859m-19.5.338V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18v-4.162c0-.224-.034-.447-.1-.661L19.24 5.338a2.25 2.25 0 0 0-2.15-1.588H6.911a2.25 2.25 0 0 0-2.15 1.588L2.35 13.177a2.25 2.25 0 0 0-.1.661Z" />
+ </svg>
+ </div>
+ <span class="text-xs font-medium text-zinc-500 uppercase tracking-wider">Mailboxes</span>
+ </div>
+ <p class="text-3xl font-bold text-zinc-100 tracking-tight">0</p>
+ <p class="mt-1 text-xs text-zinc-600">Active inboxes</p>
+ </div>
+
+ <div class="glass rounded-xl p-5 glow-border">
+ <div class="flex items-center gap-3 mb-4">
+ <div class="flex items-center justify-center w-9 h-9 rounded-lg bg-accent-500/10">
+ <svg class="w-4.5 h-4.5 text-accent-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" />
+ </svg>
+ </div>
+ <span class="text-xs font-medium text-zinc-500 uppercase tracking-wider">Emails</span>
+ </div>
+ <p class="text-3xl font-bold text-zinc-100 tracking-tight">0</p>
+ <p class="mt-1 text-xs text-zinc-600">Total received</p>
+ </div>
+
+ <div class="glass rounded-xl p-5 glow-border">
+ <div class="flex items-center gap-3 mb-4">
+ <div class="flex items-center justify-center w-9 h-9 rounded-lg bg-emerald-500/10">
+ <svg class="w-4.5 h-4.5 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9.348 14.652a3.75 3.75 0 0 1 0-5.304m5.304 0a3.75 3.75 0 0 1 0 5.304m-7.425 2.121a6.75 6.75 0 0 1 0-9.546m9.546 0a6.75 6.75 0 0 1 0 9.546M5.106 18.894c-3.808-3.807-3.808-9.98 0-13.788m13.788 0c3.808 3.807 3.808 9.98 0 13.788M12 12h.008v.008H12V12Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
+ </svg>
+ </div>
+ <span class="text-xs font-medium text-zinc-500 uppercase tracking-wider">Server</span>
+ </div>
+ <p class="text-3xl font-bold text-emerald-400 tracking-tight">Online</p>
+ <p class="mt-1 text-xs text-zinc-600">SMTP listening on :1025</p>
+ </div>
+ </div>
+
+ <div class="glass rounded-xl glow-border">
+ <div class="flex items-center justify-between px-5 py-4 border-b border-white/[0.04]">
+ <h2 class="text-sm font-medium text-zinc-200">Recent Emails</h2>
+ </div>
+ <div class="flex flex-col items-center justify-center py-16 text-center">
+ <div class="flex items-center justify-center w-12 h-12 rounded-2xl bg-surface-800 mb-4">
+ <svg class="w-6 h-6 text-zinc-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" />
+ </svg>
+ </div>
+ <p class="text-sm text-zinc-400">No emails yet</p>
+ <p class="mt-1 text-xs text-zinc-600">Send an email to any address and it will appear here</p>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/templates/dashboard/htmx/users.htmx.django b/templates/dashboard/htmx/users.htmx.django
new file mode 100644
index 0000000..7a36fe8
--- /dev/null
+++ b/templates/dashboard/htmx/users.htmx.django
@@ -0,0 +1,29 @@
+<h1 id="page-title" class="text-sm font-medium text-zinc-100" hx-swap-oob="true">{{ PageTitle }}</h1>
+<div class="slide-up space-y-6">
+ <div class="glass rounded-xl glow-border">
+ <div class="flex items-center justify-between px-5 py-4 border-b border-white/[0.04]">
+ <h2 class="text-sm font-medium text-zinc-200">All Users</h2>
+ </div>
+ {% if AuthEnabled %}
+ <div class="flex flex-col items-center justify-center py-16 text-center">
+ <div class="flex items-center justify-center w-12 h-12 rounded-2xl bg-surface-800 mb-4">
+ <svg class="w-6 h-6 text-zinc-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" />
+ </svg>
+ </div>
+ <p class="text-sm text-zinc-400">No additional users</p>
+ <p class="mt-1 text-xs text-zinc-600">Users are configured in config.toml</p>
+ </div>
+ {% else %}
+ <div class="flex flex-col items-center justify-center py-16 text-center">
+ <div class="flex items-center justify-center w-12 h-12 rounded-2xl bg-surface-800 mb-4">
+ <svg class="w-6 h-6 text-zinc-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H6.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z" />
+ </svg>
+ </div>
+ <p class="text-sm text-zinc-400">Authentication is disabled</p>
+ <p class="mt-1 text-xs text-zinc-600">Enable authentication in config.toml to manage users</p>
+ </div>
+ {% endif %}
+ </div>
+</div> \ No newline at end of file
diff --git a/templates/dashboard/mailbox.django b/templates/dashboard/mailbox.django
new file mode 100644
index 0000000..7f0e312
--- /dev/null
+++ b/templates/dashboard/mailbox.django
@@ -0,0 +1,16 @@
+{% extends "layouts/dashboard.django" %}
+
+{% block header_actions %}
+<div class="flex items-center gap-3">
+ <div class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-surface-800 border border-white/[0.06]">
+ <svg class="w-3.5 h-3.5 text-accent-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" />
+ </svg>
+ <span class="text-xs text-zinc-300">{{ Address }}</span>
+ </div>
+</div>
+{% endblock %}
+
+{% block dashboard %}
+{% include "dashboard/htmx/mailbox.htmx.django" %}
+{% endblock %} \ No newline at end of file
diff --git a/templates/dashboard/mailboxes.django b/templates/dashboard/mailboxes.django
new file mode 100644
index 0000000..5c7e255
--- /dev/null
+++ b/templates/dashboard/mailboxes.django
@@ -0,0 +1,5 @@
+{% extends "layouts/dashboard.django" %}
+
+{% block dashboard %}
+{% include "dashboard/htmx/mailboxes.htmx.django" %}
+{% endblock %} \ No newline at end of file
diff --git a/templates/dashboard/overview.django b/templates/dashboard/overview.django
new file mode 100644
index 0000000..a5c4ff4
--- /dev/null
+++ b/templates/dashboard/overview.django
@@ -0,0 +1,5 @@
+{% extends "layouts/dashboard.django" %}
+
+{% block dashboard %}
+{% include "dashboard/htmx/overview.htmx.django" %}
+{% endblock %} \ No newline at end of file
diff --git a/templates/dashboard/users.django b/templates/dashboard/users.django
new file mode 100644
index 0000000..e00656f
--- /dev/null
+++ b/templates/dashboard/users.django
@@ -0,0 +1,5 @@
+{% extends "layouts/dashboard.django" %}
+
+{% block dashboard %}
+{% include "dashboard/htmx/users.htmx.django" %}
+{% endblock %} \ No newline at end of file
diff --git a/templates/layouts/base.django b/templates/layouts/base.django
index 1ccc991..0a884dd 100644
--- a/templates/layouts/base.django
+++ b/templates/layouts/base.django
@@ -7,13 +7,15 @@
<link rel="stylesheet" href="/static/css/style.css">
{% block head %}{% endblock %}
</head>
-<body class="antialiased bg-surface-950 text-zinc-200">
+<body class="antialiased bg-surface-950 text-zinc-200" hx-indicator="#loading-bar">
+ <div class="loading-bar" id="loading-bar"></div>
<div class="fixed inset-0 -z-10 overflow-hidden">
<div class="absolute -top-40 -right-40 h-[500px] w-[500px] rounded-full bg-accent-500/[0.03] blur-[120px]"></div>
<div class="absolute -bottom-40 -left-40 h-[400px] w-[400px] rounded-full bg-accent-400/[0.02] blur-[100px]"></div>
</div>
{% block content %}{% endblock %}
<script src="/static/js/htmx.min.js"></script>
+ <script src="/static/js/navigation.js"></script>
{% block scripts %}{% endblock %}
</body>
</html>
diff --git a/templates/layouts/dashboard.django b/templates/layouts/dashboard.django
index 22f03bc..b8713a6 100644
--- a/templates/layouts/dashboard.django
+++ b/templates/layouts/dashboard.django
@@ -1,61 +1,13 @@
{% extends "layouts/base.django" %}
{% block content %}
-<div class="min-h-screen flex fade-in">
- <aside class="w-60 shrink-0 border-r border-white/[0.04] glass">
- <div class="flex items-center gap-3 px-5 h-14 border-b border-white/[0.04]">
- <div class="flex items-center justify-center w-8 h-8 rounded-lg bg-accent-500/10">
- <svg class="w-4 h-4 text-accent-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
- <path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" />
- </svg>
- </div>
- <span class="text-sm font-semibold text-zinc-100 tracking-tight">Dove</span>
- </div>
+<div class="min-h-screen flex" id="dashboard">
+ {% include "partials/sidebar.django" %}
- <nav class="flex flex-col gap-1 p-3">
- <a href="/dashboard" class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm text-zinc-400 hover:text-zinc-200 hover:bg-white/[0.04] transition-colors duration-150 {% if ActiveNav == 'overview' %}bg-white/[0.06] text-zinc-100{% endif %}">
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
- <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6ZM3.75 15.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6ZM13.5 15.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25a2.25 2.25 0 0 1-2.25-2.25v-2.25Z" />
- </svg>
- Overview
- </a>
- <a href="/dashboard/mailboxes" class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm text-zinc-400 hover:text-zinc-200 hover:bg-white/[0.04] transition-colors duration-150 {% if ActiveNav == 'mailboxes' %}bg-white/[0.06] text-zinc-100{% endif %}">
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
- <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 13.5h3.86a2.25 2.25 0 0 1 2.012 1.244l.256.512a2.25 2.25 0 0 0 2.013 1.244h2.21a2.25 2.25 0 0 0 2.013-1.244l.256-.512a2.25 2.25 0 0 1 2.013-1.244h3.859m-19.5.338V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18v-4.162c0-.224-.034-.447-.1-.661L19.24 5.338a2.25 2.25 0 0 0-2.15-1.588H6.911a2.25 2.25 0 0 0-2.15 1.588L2.35 13.177a2.25 2.25 0 0 0-.1.661Z" />
- </svg>
- Mailboxes
- </a>
- <a href="/dashboard/users" class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm text-zinc-400 hover:text-zinc-200 hover:bg-white/[0.04] transition-colors duration-150 {% if ActiveNav == 'users' %}bg-white/[0.06] text-zinc-100{% endif %}">
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
- <path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" />
- </svg>
- Users
- </a>
- </nav>
+ <div class="flex-1 flex flex-col min-h-screen" id="panel">
+ {% include "partials/header.django" %}
- <div class="mt-auto p-3 border-t border-white/[0.04]">
- <div class="flex items-center gap-2 px-3 py-2 text-xs text-zinc-600">
- <span class="inline-block w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
- SMTP listening
- </div>
- {% if AuthEnabled %}
- <a href="/auth/logout" class="flex items-center gap-3 px-3 py-2 rounded-lg text-xs text-zinc-500 hover:text-zinc-300 hover:bg-white/[0.04] transition-colors duration-150">
- <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
- <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15m3 0 3-3m0 0-3-3m3 3H9" />
- </svg>
- Logout
- </a>
- {% endif %}
- </div>
- </aside>
-
- <div class="flex-1 flex flex-col min-h-screen">
- <header class="h-14 flex items-center justify-between px-8 border-b border-white/[0.04]">
- <h1 class="text-sm font-medium text-zinc-100">{{ PageTitle }}</h1>
- {% block header_actions %}{% endblock %}
- </header>
-
- <main class="flex-1 p-8">
+ <main class="flex-1 p-8" id="content">
{% block dashboard %}{% endblock %}
</main>
</div>
diff --git a/templates/partials/header.django b/templates/partials/header.django
new file mode 100644
index 0000000..5af34eb
--- /dev/null
+++ b/templates/partials/header.django
@@ -0,0 +1,4 @@
+<header class="h-14 flex items-center justify-between px-8 border-b border-white/[0.04]">
+ <h1 id="page-title" class="text-sm font-medium text-zinc-100">{{ PageTitle }}</h1>
+ {% block header_actions %}{% endblock %}
+</header>
diff --git a/templates/partials/sidebar.django b/templates/partials/sidebar.django
new file mode 100644
index 0000000..c1e9623
--- /dev/null
+++ b/templates/partials/sidebar.django
@@ -0,0 +1,49 @@
+<aside class="w-60 shrink-0 border-r border-white/[0.04] glass">
+ <div class="flex items-center gap-3 px-5 h-14 border-b border-white/[0.04]">
+ <div class="flex items-center justify-center w-8 h-8 rounded-lg bg-accent-500/10">
+ <svg class="w-4 h-4 text-accent-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" />
+ </svg>
+ </div>
+ <span class="text-sm font-semibold text-zinc-100 tracking-tight">Dove</span>
+ </div>
+
+ <nav class="flex flex-col gap-1 p-3" id="sidebar-nav" hx-target="#content" hx-swap="innerHTML" hx-push-url="true">
+ {% url "dashboard.index" as overview_path %}
+ {% url "dashboard.mailboxes" as mailboxes_path %}
+ {% url "dashboard.users" as users_path %}
+ <a href="{{ overview_path }}" hx-get="{{ overview_path }}" class="nav-link flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors duration-150 {% if Request.Path == overview_path %}active{% endif %}">
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6ZM3.75 15.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6ZM13.5 15.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25a2.25 2.25 0 0 1-2.25-2.25v-2.25Z" />
+ </svg>
+ Overview
+ </a>
+ <a href="{{ mailboxes_path }}" hx-get="{{ mailboxes_path }}" class="nav-link flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors duration-150 {% if Request.Path == mailboxes_path %}active{% endif %}">
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 13.5h3.86a2.25 2.25 0 0 1 2.012 1.244l.256.512a2.25 2.25 0 0 0 2.013 1.244h2.21a2.25 2.25 0 0 0 2.013-1.244l.256-.512a2.25 2.25 0 0 1 2.013-1.244h3.859m-19.5.338V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18v-4.162c0-.224-.034-.447-.1-.661L19.24 5.338a2.25 2.25 0 0 0-2.15-1.588H6.911a2.25 2.25 0 0 0-2.15 1.588L2.35 13.177a2.25 2.25 0 0 0-.1.661Z" />
+ </svg>
+ Mailboxes
+ </a>
+ <a href="{{ users_path }}" hx-get="{{ users_path }}" class="nav-link flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors duration-150 {% if Request.Path == users_path %}active{% endif %}">
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" />
+ </svg>
+ Users
+ </a>
+ </nav>
+
+ <div class="mt-auto p-3 border-t border-white/[0.04]">
+ <div class="flex items-center gap-2 px-3 py-2 text-xs text-zinc-600">
+ <span class="inline-block w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
+ SMTP listening
+ </div>
+ {% if AuthEnabled %}
+ <a href="/auth/logout" class="flex items-center gap-3 px-3 py-2 rounded-lg text-xs text-zinc-500 hover:text-zinc-300 hover:bg-white/[0.04] transition-colors duration-150">
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15m3 0 3-3m0 0-3-3m3 3H9" />
+ </svg>
+ Logout
+ </a>
+ {% endif %}
+ </div>
+</aside>
diff --git a/utils/meta/constants.go b/utils/meta/constants.go
index 3f2ce68..0138884 100644
--- a/utils/meta/constants.go
+++ b/utils/meta/constants.go
@@ -2,5 +2,5 @@ package meta
const (
LOG_PREFIX = "Meta"
- REQUEST_KEY = "__request"
+ REQUEST_KEY = "Request"
)
diff --git a/utils/shortcuts/functions.go b/utils/shortcuts/functions.go
index df00dcf..3e76a9a 100644
--- a/utils/shortcuts/functions.go
+++ b/utils/shortcuts/functions.go
@@ -1,7 +1,9 @@
package shortcuts
import (
+ "fmt"
"maps"
+ "path"
"reflect"
"strings"
@@ -11,6 +13,17 @@ import (
"github.com/gofiber/fiber/v2"
)
+func resolveTemplate(context *fiber.Ctx, templateName string) string {
+ switch {
+ case context.Get("HX-Request") == "true" && context.Get("HX-Boosted") != "true":
+ directory := path.Dir(templateName)
+ filename := path.Base(templateName)
+ return fmt.Sprintf("%s/htmx/%s.htmx", directory, filename)
+ default:
+ return templateName
+ }
+}
+
func mergeContextValues(context *fiber.Ctx, targetMap fiber.Map) {
context.Context().VisitUserValues(func(key []byte, value any) {
targetMap[string(key)] = value
diff --git a/utils/shortcuts/render.go b/utils/shortcuts/render.go
index 29c7a8f..e91ccfa 100644
--- a/utils/shortcuts/render.go
+++ b/utils/shortcuts/render.go
@@ -13,7 +13,7 @@ func Render(context *fiber.Ctx, templateName string, data any) error {
}
}
- return context.Render(templateName, templateData)
+ return context.Render(resolveTemplate(context, templateName), templateData)
}
func RenderWithStatus(context *fiber.Ctx, templateName string, data any, statusCode int) error {