diff --git a/public/pages/login.js b/public/pages/login.js index 464317e..8950593 100644 --- a/public/pages/login.js +++ b/public/pages/login.js @@ -54,7 +54,7 @@ export async function render(container) {
@@ -84,8 +84,14 @@ export async function render(container) { return; } + const labelEl = submitBtn.querySelector('.login-btn__label'); + submitBtn.disabled = true; - submitBtn.textContent = t('login.loggingIn'); + labelEl.textContent = t('login.loggingIn'); + const spinner = document.createElement('span'); + spinner.className = 'login-spinner'; + spinner.setAttribute('aria-hidden', 'true'); + submitBtn.insertBefore(spinner, labelEl); try { const result = await auth.login(username, password); @@ -97,7 +103,8 @@ export async function render(container) { ); } finally { submitBtn.disabled = false; - submitBtn.textContent = t('login.loginButton'); + labelEl.textContent = t('login.loginButton'); + spinner.remove(); } }); } diff --git a/public/styles/login.css b/public/styles/login.css index 7a064a7..13775ae 100644 --- a/public/styles/login.css +++ b/public/styles/login.css @@ -45,6 +45,10 @@ .login-form__submit { width: 100%; margin-top: var(--space-2); + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-2); } .login-error { @@ -63,3 +67,21 @@ text-align: center; opacity: 0.6; } + +.login-spinner { + width: 16px; + height: 16px; + border: 2px solid color-mix(in srgb, currentColor 30%, transparent); + border-top-color: currentColor; + border-radius: 50%; + animation: login-spin 0.7s linear infinite; + flex-shrink: 0; +} + +@keyframes login-spin { + to { transform: rotate(360deg); } +} + +@media (prefers-reduced-motion: reduce) { + .login-spinner { animation: none; opacity: 0.6; } +}