Skip to content

Commit 71a90df

Browse files
appleboyclaude
andcommitted
refactor(templates): fix dark mode detection and clean up dead code
- Fix keydown listener leak in confirmation modal close handler - Restore data-confirm-title attribute after form submission - Remove dead functions: initTheme, confirmAction, confirmDelete, confirmRegenerateSecret - Reuse copyToClipboard in admin copySecret instead of duplicating logic - Consolidate repeated dark-mode focus ring CSS into single selector - Remove unnecessary toast container dynamic creation fallback - Extract shared SearchIcon templ component replacing three inline SVGs - Ensure inline theme script always sets data-theme attribute - Remove redundant prefers-color-scheme CSS media query from base.css Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8a5802f commit 71a90df

10 files changed

Lines changed: 116 additions & 232 deletions

File tree

internal/templates/account_sessions.templ

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ templ AccountSessions(props SessionsPageProps) {
2525
<label for="search" class="search-label">Search</label>
2626
<div class="search-input-wrapper">
2727
<span class="search-icon">
28-
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
28+
@SearchIcon()
2929
</span>
3030
<input
3131
type="text"
@@ -293,8 +293,7 @@ func expiryPercent(created, expires time.Time) int {
293293
return pct
294294
}
295295

296-
func expiryColorClass(created, expires time.Time) string {
297-
pct := expiryPercent(created, expires)
296+
func expiryBarClass(pct int) string {
298297
if pct >= 100 {
299298
return "expiry-expired"
300299
}
@@ -309,7 +308,7 @@ func expiryColorClass(created, expires time.Time) string {
309308

310309
templ TokenExpiryBar(created, expires time.Time) {
311310
<div
312-
class={ "token-expiry-bar " + expiryColorClass(created, expires) }
311+
class={ "token-expiry-bar " + expiryBarClass(expiryPercent(created, expires)) }
313312
title={ fmt.Sprintf("%d%% elapsed", expiryPercent(created, expires)) }
314313
>
315314
<div class="token-expiry-bar-fill" style={ fmt.Sprintf("width:%d%%", expiryPercent(created, expires)) }></div>

internal/templates/admin_audit_logs.templ

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ templ AdminAuditLogs(props AuditLogsPageProps) {
3737
<label class="search-label">Search</label>
3838
<div class="search-input-wrapper">
3939
<span class="search-icon">
40-
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
40+
@SearchIcon()
4141
</span>
4242
<input
4343
type="text"

internal/templates/client_shared.templ

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ templ ClientsSearchBar(action, search string, pageSize int) {
2828
<div class="search-group">
2929
<label for="search" class="search-label">Search</label>
3030
<div class="search-input-wrapper">
31-
<span class="search-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg></span>
31+
<span class="search-icon">@SearchIcon()</span>
3232
<input
3333
type="text"
3434
id="search"

internal/templates/empty_state_icons.templ

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package templates
22

3+
// SearchIcon renders a shared search magnifying glass SVG icon.
4+
templ SearchIcon() {
5+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
6+
}
7+
38
// EmptyStateSessions renders an SVG icon for empty session states.
49
templ EmptyStateSessions() {
510
<svg class="empty-icon-svg" width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">

internal/templates/layout_component.templ

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ templ Layout(title string, layoutType LayoutType, navbar *NavbarProps) {
2323
<title>{ title } - AuthGate</title>
2424
<!-- Prevent FOUC: apply saved theme before paint -->
2525
<script>
26-
(function(){var t=localStorage.getItem('authgate-theme');if(t==='dark'||t==='light'){document.documentElement.setAttribute('data-theme',t)}})();
26+
(function(){var t=localStorage.getItem('authgate-theme');if(t==='dark'||t==='light'){document.documentElement.setAttribute('data-theme',t)}else if(window.matchMedia&&window.matchMedia('(prefers-color-scheme:dark)').matches){document.documentElement.setAttribute('data-theme','dark')}else{document.documentElement.setAttribute('data-theme','light')}})();
2727
</script>
2828
<!-- Preconnect to font CDN -->
2929
<link rel="preconnect" href="https://fonts.googleapis.com"/>

internal/templates/static/css/base.css

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -147,51 +147,6 @@
147147
--shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.6);
148148
}
149149

150-
@media (prefers-color-scheme: dark) {
151-
:root:not([data-theme="light"]) {
152-
--color-primary: #58a6ff;
153-
--color-primary-dark: #79c0ff;
154-
--color-primary-light: #388bfd;
155-
--color-primary-pale: #0d2240;
156-
157-
--color-success: #3fb950;
158-
--color-success-pale: #0d2d1b;
159-
--color-error: #f85149;
160-
--color-error-pale: #3d1214;
161-
--color-warning: #d29922;
162-
--color-warning-pale: #3b2e08;
163-
164-
--color-gray-50: #161b22;
165-
--color-gray-100: #21262d;
166-
--color-gray-200: #30363d;
167-
--color-gray-300: #484f58;
168-
--color-gray-400: #6e7681;
169-
--color-gray-500: #8b949e;
170-
--color-gray-600: #b1bac4;
171-
--color-gray-700: #c9d1d9;
172-
--color-gray-800: #e6edf3;
173-
--color-gray-900: #f0f6fc;
174-
175-
--color-bg-primary: #0d1117;
176-
--color-bg-secondary: #161b22;
177-
--color-bg-tertiary: #21262d;
178-
179-
--color-text-primary: #e6edf3;
180-
--color-text-secondary: #8b949e;
181-
--color-text-tertiary: #6e7681;
182-
--color-text-inverse: #0d1117;
183-
184-
--color-border: #30363d;
185-
--color-border-focus: #58a6ff;
186-
187-
--shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
188-
--shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.4), 0 1px 2px 0 rgba(0, 0, 0, 0.3);
189-
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
190-
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3);
191-
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.3);
192-
--shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.6);
193-
}
194-
}
195150

196151
/* ============================================
197152
CSS Reset & Base Elements

internal/templates/static/css/components/dark-mode.css

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,68 @@
44
other elements that don't use CSS variables
55
============================================ */
66

7+
/* Form focus rings — hardcoded rgba is too faint on dark backgrounds */
8+
[data-theme="dark"] .form-input:focus,
9+
[data-theme="dark"] input[type="text"]:focus,
10+
[data-theme="dark"] input[type="password"]:focus,
11+
[data-theme="dark"] input[type="email"]:focus,
12+
[data-theme="dark"] .form-select:focus,
13+
[data-theme="dark"] select:focus,
14+
[data-theme="dark"] .form-textarea:focus,
15+
[data-theme="dark"] textarea:focus,
16+
[data-theme="dark"] .search-input:focus,
17+
[data-theme="dark"] .audit-filter-select:focus,
18+
[data-theme="dark"] .audit-filter-input:focus,
19+
[data-theme="dark"] .page-size-select:focus {
20+
box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.25);
21+
}
22+
23+
/* Navbar — ensure readable contrast */
24+
[data-theme="dark"] .navbar {
25+
background: rgba(13, 17, 23, 0.95);
26+
}
27+
28+
/* OAuth buttons — ensure contrast on dark cards */
29+
[data-theme="dark"] .btn-oauth.btn-github {
30+
background-color: #30363d;
31+
border: 1px solid var(--color-border);
32+
}
33+
34+
[data-theme="dark"] .btn-oauth.btn-microsoft {
35+
background-color: #21262d;
36+
border: 1px solid var(--color-border);
37+
}
38+
39+
/* Auth divider background must match card */
40+
[data-theme="dark"] .auth-divider-text {
41+
background: var(--color-bg-primary);
42+
}
43+
44+
/* User info badge */
45+
[data-theme="dark"] .user-info {
46+
background: var(--color-bg-tertiary);
47+
}
48+
49+
/* Code/pre blocks */
50+
[data-theme="dark"] .grant-types-code {
51+
background: var(--color-bg-tertiary);
52+
border-color: var(--color-border);
53+
color: var(--color-text-primary);
54+
}
55+
56+
[data-theme="dark"] .client-id-box {
57+
background: var(--color-bg-tertiary);
58+
border-color: var(--color-border);
59+
color: var(--color-text-secondary);
60+
}
61+
62+
/* Audit event type badge */
63+
[data-theme="dark"] .audit-event-type {
64+
background: var(--color-bg-tertiary);
65+
border-color: var(--color-border);
66+
color: var(--color-text-primary);
67+
}
68+
769
/* Status badges with inline styles (hardcoded rgba colors) */
870
[data-theme="dark"] .status-badge[style*="rgba(245,158,11"] {
971
background: rgba(245, 158, 11, 0.2) !important;

internal/templates/static/js/admin.js

Lines changed: 10 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -3,102 +3,42 @@
33
* Functions for admin interface interactions
44
*/
55

6+
import { copyToClipboard } from './utils.js';
7+
68
/**
79
* Copy client secret to clipboard
810
*/
911
function copySecret() {
10-
const secretElement = document.getElementById('clientSecret');
12+
var secretElement = document.getElementById('clientSecret');
1113
if (!secretElement) {
1214
console.error('Secret element not found');
1315
return;
1416
}
15-
16-
const secret = secretElement.textContent;
17-
18-
// Use modern clipboard API if available
19-
if (navigator.clipboard && navigator.clipboard.writeText) {
20-
navigator.clipboard.writeText(secret)
21-
.then(function() {
22-
showNotification('Client secret copied to clipboard!', 'success');
23-
})
24-
.catch(function(err) {
25-
console.error('Failed to copy:', err);
26-
fallbackCopySecret(secret);
27-
});
28-
} else {
29-
// Fallback for older browsers
30-
fallbackCopySecret(secret);
31-
}
32-
}
33-
34-
/**
35-
* Fallback method to copy text for older browsers
36-
*/
37-
function fallbackCopySecret(text) {
38-
const textarea = document.createElement('textarea');
39-
textarea.value = text;
40-
textarea.style.position = 'fixed';
41-
textarea.style.opacity = '0';
42-
document.body.appendChild(textarea);
43-
textarea.select();
44-
45-
try {
46-
const successful = document.execCommand('copy');
47-
if (successful) {
48-
showNotification('Client secret copied to clipboard!', 'success');
49-
} else {
50-
showNotification('Failed to copy secret', 'error');
51-
}
52-
} catch (err) {
53-
console.error('Failed to copy:', err);
54-
showNotification('Failed to copy secret', 'error');
55-
}
56-
57-
document.body.removeChild(textarea);
58-
}
59-
60-
/**
61-
* Show notification (uses utils.js if available)
62-
*/
63-
function showNotification(message, type) {
64-
// Try to use the notification function from utils.js
65-
if (typeof window.showNotification === 'function') {
66-
window.showNotification(message, type);
67-
return;
68-
}
69-
70-
// Fallback to alert if utils.js is not loaded
71-
alert(message);
17+
copyToClipboard(secretElement.textContent);
7218
}
7319

7420
/**
7521
* Toggle client description in table
7622
*/
7723
function toggleDescription(button) {
78-
const cell = button.closest('.client-name-cell');
24+
var cell = button.closest('.client-name-cell');
7925
if (!cell) return;
8026

81-
const description = cell.querySelector('.client-description');
82-
const icon = button.querySelector('.toggle-icon');
83-
27+
var description = cell.querySelector('.client-description');
8428
if (!description) return;
8529

8630
if (description.style.display === 'none' || !description.style.display) {
87-
// Show description
8831
description.style.display = 'block';
8932
button.classList.add('expanded');
9033

91-
// Animate height
9234
description.style.maxHeight = '0';
9335
description.style.overflow = 'hidden';
9436
description.style.transition = 'max-height 0.3s ease-out';
9537

96-
// Trigger reflow
9738
description.offsetHeight;
9839

9940
description.style.maxHeight = description.scrollHeight + 'px';
10041
} else {
101-
// Hide description
10242
description.style.maxHeight = '0';
10343
button.classList.remove('expanded');
10444

@@ -108,42 +48,18 @@ function toggleDescription(button) {
10848
}
10949
}
11050

111-
/**
112-
* Confirm delete action
113-
*/
114-
function confirmDelete(clientName) {
115-
return confirm(
116-
'Are you sure you want to delete this client?\n\n' +
117-
'Client: ' + clientName + '\n\n' +
118-
'This action cannot be undone and will revoke all access tokens.'
119-
);
120-
}
121-
122-
/**
123-
* Confirm regenerate secret
124-
*/
125-
function confirmRegenerateSecret() {
126-
return confirm(
127-
'Are you sure you want to regenerate the client secret?\n\n' +
128-
'This will invalidate the current secret and any applications using it will stop working until updated with the new secret.'
129-
);
130-
}
131-
132-
export { copySecret, toggleDescription, confirmDelete, confirmRegenerateSecret };
51+
export { copySecret, toggleDescription };
13352

13453
/**
13554
* Initialize admin page interactions
13655
*/
13756
document.addEventListener('DOMContentLoaded', function() {
138-
// Add any initialization code here
139-
140-
// Example: Auto-select secret on click
141-
const secretElements = document.querySelectorAll('.secret-value, .secret-value-enhanced');
57+
var secretElements = document.querySelectorAll('.secret-value, .secret-value-enhanced');
14258
secretElements.forEach(function(element) {
14359
element.addEventListener('click', function() {
144-
const range = document.createRange();
60+
var range = document.createRange();
14561
range.selectNodeContents(element);
146-
const selection = window.getSelection();
62+
var selection = window.getSelection();
14763
selection.removeAllRanges();
14864
selection.addRange(range);
14965
});

internal/templates/static/js/main.js

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
import { toggleMenu, toggleDropdown } from './navbar.js';
2-
import { copySecret, toggleDescription, confirmDelete, confirmRegenerateSecret } from './admin.js';
2+
import { copySecret, toggleDescription } from './admin.js';
33
import {
44
formatRelativeTime,
55
copyToClipboard,
66
showNotification,
7-
confirmAction,
87
confirmModal,
98
toggleDetails,
10-
initTheme,
119
toggleTheme,
12-
initDebounceSearch,
13-
toggleFilters,
14-
initCollapsibleFilters
10+
toggleFilters
1511
} from './utils.js';
1612
import './code-formatter.js';
1713

@@ -20,12 +16,9 @@ window.toggleMenu = toggleMenu;
2016
window.toggleDropdown = toggleDropdown;
2117
window.copySecret = copySecret;
2218
window.toggleDescription = toggleDescription;
23-
window.confirmDelete = confirmDelete;
24-
window.confirmRegenerateSecret = confirmRegenerateSecret;
2519
window.formatRelativeTime = formatRelativeTime;
2620
window.copyToClipboard = copyToClipboard;
2721
window.showNotification = showNotification;
28-
window.confirmAction = confirmAction;
2922
window.confirmModal = confirmModal;
3023
window.toggleDetails = toggleDetails;
3124
window.toggleTheme = toggleTheme;

0 commit comments

Comments
 (0)