feat: add loading state and spinner animation
This commit is contained in:
+38
-26
@@ -9,16 +9,6 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@5/themes.css" rel="stylesheet" type="text/css" />
|
||||
<script src="https://unpkg.com/@phosphor-icons/web@2.1.1"></script>
|
||||
<style>
|
||||
.font-mono {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
.ph {
|
||||
font-size: 1.25rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-base-200/50 min-h-screen font-sans antialiased text-base-content">
|
||||
@@ -29,19 +19,20 @@
|
||||
class="w-8 h-8 bg-primary rounded-lg flex items-center justify-center text-primary-content font-bold text-lg">
|
||||
<i class="ph-duotone ph-fingerprint"></i>
|
||||
</div>
|
||||
<a class="text-xl font-bold tracking-tight">toknd <span
|
||||
class="text-xs font-normal opacity-50 ml-1">auth broker</span></a>
|
||||
<a class="text-xl font-bold tracking-tight">toknd <span class="text-xs font-normal opacity-50 ml-1">auth
|
||||
broker</span></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-none hidden sm:flex">
|
||||
<div class="join shadow-inner bg-base-200/50 border border-base-300 rounded-xl overflow-hidden transition-all focus-within:ring-2 focus-within:ring-primary/20">
|
||||
<div
|
||||
class="join shadow-inner bg-base-200/50 border border-base-300 rounded-xl overflow-hidden transition-all focus-within:ring-2 focus-within:ring-primary/20">
|
||||
<div class="join-item flex items-center px-4 border-r border-base-300/50 bg-base-100">
|
||||
<i class="ph-duotone ph-key text-primary text-lg animate-pulse-slow"></i>
|
||||
</div>
|
||||
<div class="relative flex-1">
|
||||
<input type="password" id="apiKey" placeholder="Unlock Registry..."
|
||||
class="input join-item input-sm bg-transparent border-none focus:outline-none focus:ring-0 w-48 lg:w-64 font-mono text-xs pr-10" />
|
||||
<button type="button" onclick="window.toggleToken('apiKey')"
|
||||
<input type="password" id="apiKey" placeholder="API_KEY"
|
||||
class="input join-item input-sm bg-transparent border-none focus:outline-none focus:ring-0 w-48 lg:w-64 text-xs pr-10" />
|
||||
<button type="button" onclick="window.toggleToken('apiKey')"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 btn btn-ghost btn-xs btn-square">
|
||||
<i class="ph-duotone ph-eye text-base opacity-50" id="eye-apiKey"></i>
|
||||
</button>
|
||||
@@ -108,7 +99,7 @@
|
||||
<div class="relative">
|
||||
<input type="password" id="clientSecret" placeholder="OAuth client secret" required
|
||||
class="input input-bordered w-full focus:input-primary pr-12" />
|
||||
<button type="button" onclick="window.toggleToken('clientSecret')"
|
||||
<button type="button" onclick="window.toggleToken('clientSecret')"
|
||||
class="absolute right-3 top-1/2 -translate-y-1/2 btn btn-ghost btn-xs btn-square">
|
||||
<i class="ph-duotone ph-eye text-lg opacity-40" id="eye-clientSecret"></i>
|
||||
</button>
|
||||
@@ -199,16 +190,28 @@
|
||||
<h2 class="card-title text-xl font-bold">Provider Registry</h2>
|
||||
</div>
|
||||
<button type="button" onclick="fetchProviders()" class="btn btn-sm btn-ghost border-base-300">
|
||||
<i class="ph ph-arrows-clockwise mr-1"></i>
|
||||
<i id="refreshIcon" class="ph ph-arrows-clockwise mr-1"></i>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div id="providerRegistry" class="p-6 grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Empty State -->
|
||||
<div class="col-span-full text-center py-20 opacity-30">
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<i class="ph ph-lock-key text-6xl"></i>
|
||||
<p class="font-medium">Enter Master API Key to access registry</p>
|
||||
<div class="relative min-h-[400px]">
|
||||
<!-- Loading Overlay -->
|
||||
<div id="registryLoading"
|
||||
class="absolute inset-0 bg-base-100/50 backdrop-blur-[2px] z-10 flex items-center justify-center hidden transition-all duration-300">
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||
<span class="text-xs font-bold uppercase tracking-widest opacity-40">Syncing
|
||||
Registry...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="providerRegistry" class="p-6 grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Empty State -->
|
||||
<div class="col-span-full text-center py-20 opacity-30">
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<i class="ph ph-lock-key text-6xl"></i>
|
||||
<p class="font-medium">Enter Master API Key to access registry</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -228,6 +231,7 @@
|
||||
const configForm = document.getElementById('configForm');
|
||||
const redirectUriInput = document.getElementById('redirectUri');
|
||||
const providerRegistry = document.getElementById('providerRegistry');
|
||||
const registryLoading = document.getElementById('registryLoading');
|
||||
|
||||
function setDefaultRedirectUri() {
|
||||
if (redirectUriInput) {
|
||||
@@ -264,6 +268,8 @@
|
||||
|
||||
async function fetchProviders() {
|
||||
const apiKey = apiKeyInput.value.trim();
|
||||
const refreshIcon = document.getElementById('refreshIcon');
|
||||
|
||||
if (!apiKey) {
|
||||
providerRegistry.innerHTML = `
|
||||
<div class="col-span-full text-center py-20 opacity-30">
|
||||
@@ -275,6 +281,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (refreshIcon) refreshIcon.classList.add('animate-spin');
|
||||
if (registryLoading) registryLoading.classList.remove('hidden');
|
||||
|
||||
try {
|
||||
const configRes = await fetch('/api/config', {
|
||||
headers: { 'Authorization': `Bearer ${apiKey}` }
|
||||
@@ -294,6 +303,9 @@
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
providerRegistry.innerHTML = `<div class="col-span-full text-center text-error py-12 font-medium">${error.message}</div>`;
|
||||
} finally {
|
||||
if (refreshIcon) refreshIcon.classList.remove('animate-spin');
|
||||
if (registryLoading) registryLoading.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,7 +339,7 @@
|
||||
<div class="card-body p-5">
|
||||
<div class="flex flex-col mb-4">
|
||||
<span class="text-lg font-black tracking-tight text-base-content/90 uppercase">${name}</span>
|
||||
<span class="text-[10px] opacity-40 font-mono truncate" title="${config.clientId}">${config.clientId}</span>
|
||||
<span class="text-[10px] opacity-40 truncate" title="${config.clientId}">${config.clientId}</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
@@ -370,7 +382,7 @@
|
||||
return `
|
||||
<div class="flex items-center gap-2 bg-base-100 rounded border border-base-300 p-1 pl-3 group/token transition-all focus-within:border-primary/50">
|
||||
<input type="password" id="${id}" value="${value}" readonly
|
||||
class="bg-transparent border-none outline-none shadow-none focus:ring-0 font-mono text-[10px] flex-1 min-w-0" />
|
||||
class="bg-transparent border-none outline-none shadow-none focus:ring-0 text-[10px] flex-1 min-w-0" />
|
||||
<div class="flex gap-1">
|
||||
<button onclick="window.toggleToken('${id}')" class="btn btn-ghost btn-xs btn-square">
|
||||
<i class="ph-duotone ph-eye text-base opacity-50" id="eye-${id}"></i>
|
||||
|
||||
Reference in New Issue
Block a user