diff options
| author | Bobby <[email protected]> | 2026-03-07 17:15:38 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2026-03-07 17:15:38 +0530 |
| commit | 57df54999e778887e66775481dab46191b46d0b6 (patch) | |
| tree | 2009abf151fd34923c7a3ae760d08c3c6bbe72e0 | |
| parent | 6dd57549df7b6679a1aa9888f4d59edaaec5b3f9 (diff) | |
| download | dove-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.go | 13 | ||||
| -rw-r--r-- | pages/dashboard.go | 4 | ||||
| -rw-r--r-- | static/css/tailwind.css | 87 | ||||
| -rw-r--r-- | static/js/navigation.js | 10 | ||||
| -rw-r--r-- | tags/types.go | 5 | ||||
| -rw-r--r-- | tags/url.go | 21 | ||||
| -rw-r--r-- | templates/dashboard.django | 30 | ||||
| -rw-r--r-- | templates/dashboard/htmx/mailbox.htmx.django | 17 | ||||
| -rw-r--r-- | templates/dashboard/htmx/mailboxes.htmx.django | 18 | ||||
| -rw-r--r-- | templates/dashboard/htmx/overview.htmx.django | 58 | ||||
| -rw-r--r-- | templates/dashboard/htmx/users.htmx.django | 29 | ||||
| -rw-r--r-- | templates/dashboard/mailbox.django | 16 | ||||
| -rw-r--r-- | templates/dashboard/mailboxes.django | 5 | ||||
| -rw-r--r-- | templates/dashboard/overview.django | 5 | ||||
| -rw-r--r-- | templates/dashboard/users.django | 5 | ||||
| -rw-r--r-- | templates/layouts/base.django | 4 | ||||
| -rw-r--r-- | templates/layouts/dashboard.django | 58 | ||||
| -rw-r--r-- | templates/partials/header.django | 4 | ||||
| -rw-r--r-- | templates/partials/sidebar.django | 49 | ||||
| -rw-r--r-- | utils/meta/constants.go | 2 | ||||
| -rw-r--r-- | utils/shortcuts/functions.go | 13 | ||||
| -rw-r--r-- | utils/shortcuts/render.go | 2 |
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 { |
