Files
jellytau/src/lib/components/auth/ReauthModal.svelte
T
dtourolle 3a9c126dfe
🏗️ Build and Test JellyTau / Run Tests (push) Failing after 14s
🏗️ Build and Test JellyTau / Build Android APK (push) Has been skipped
Traceability Validation / Check Requirement Traces (push) Failing after 1s
Fix warnings and update tracability
2026-02-28 20:54:25 +01:00

169 lines
5.5 KiB
Svelte

<script lang="ts">
import { auth, authError, isAuthLoading } from "$lib/stores";
interface Props {
isOpen?: boolean;
onSuccess?: () => void;
onDismiss?: () => void;
}
let { isOpen = false, onSuccess, onDismiss }: Props = $props();
let password = $state("");
let localError = $state<string | null>(null);
let username = $state("User");
let serverName = $state("Jellyfin Server");
// Load session info asynchronously
auth.getCurrentSession().then(session => {
if (session) {
username = session.username ?? "User";
serverName = session.serverName ?? "Jellyfin Server";
}
});
async function handleSubmit(event: Event) {
event.preventDefault();
localError = null;
if (!password.trim()) {
localError = "Please enter your password";
return;
}
try {
await auth.reauthenticate(password);
password = "";
onSuccess?.();
} catch (error) {
localError = error instanceof Error ? error.message : "Authentication failed";
}
}
function handleDismiss() {
password = "";
localError = null;
onDismiss?.();
}
function handleBackdropClick(event?: MouseEvent) {
// Don't close on backdrop click - require explicit action
event?.stopPropagation();
}
</script>
{#if isOpen}
<div
class="fixed inset-0 bg-black/70 z-[100] flex items-center justify-center p-4"
onclick={handleBackdropClick}
onkeydown={(e) => { if (e.key === 'Escape') handleBackdropClick(); }}
role="dialog"
aria-modal="true"
aria-labelledby="reauth-title"
tabindex="-1"
>
<div
class="bg-[var(--color-surface)] rounded-2xl w-full max-w-sm shadow-2xl"
onclick={(e) => e.stopPropagation()}
role="none"
>
<!-- Header -->
<div class="px-6 pt-6 pb-4 text-center">
<!-- Lock icon -->
<div class="mx-auto w-16 h-16 rounded-full bg-amber-500/10 flex items-center justify-center mb-4">
<svg
class="w-8 h-8 text-amber-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
/>
</svg>
</div>
<h2 id="reauth-title" class="text-xl font-semibold text-white mb-2">
Session Expired
</h2>
<p class="text-sm text-gray-400">
Your session on <span class="text-white font-medium">{serverName}</span> has expired.
Please enter your password to continue.
</p>
</div>
<!-- Form -->
<form onsubmit={handleSubmit} class="px-6 pb-6">
<!-- Username (read-only) -->
<div class="mb-4">
<div class="block text-sm font-medium text-gray-400 mb-1" id="reauth-username-label">
Username
</div>
<div class="px-4 py-3 rounded-lg bg-gray-800/50 text-gray-300 text-sm" aria-labelledby="reauth-username-label">
{username}
</div>
</div>
<!-- Password -->
<div class="mb-4">
<label for="reauth-password" class="block text-sm font-medium text-gray-400 mb-1">
Password
</label>
<input
id="reauth-password"
type="password"
bind:value={password}
placeholder="Enter your password"
disabled={$isAuthLoading}
class="w-full px-4 py-3 rounded-lg bg-gray-800 border border-gray-700 text-white placeholder-gray-500 focus:outline-none focus:border-[var(--color-jellyfin)] focus:ring-1 focus:ring-[var(--color-jellyfin)] transition-colors disabled:opacity-50"
/>
</div>
<!-- Error message -->
{#if localError || $authError}
<div class="mb-4 p-3 rounded-lg bg-red-500/10 border border-red-500/30">
<p class="text-sm text-red-400">
{localError || $authError}
</p>
</div>
{/if}
<!-- Buttons -->
<div class="flex flex-col gap-2">
<button
type="submit"
disabled={$isAuthLoading}
class="w-full py-3 px-4 rounded-lg bg-[var(--color-jellyfin)] hover:bg-[var(--color-jellyfin-hover)] text-white font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
{#if $isAuthLoading}
<svg class="animate-spin h-5 w-5" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>Authenticating...</span>
{:else}
<span>Sign In</span>
{/if}
</button>
<button
type="button"
onclick={handleDismiss}
disabled={$isAuthLoading}
class="w-full py-3 px-4 rounded-lg border border-gray-700 hover:border-gray-600 text-gray-300 hover:text-white font-medium transition-colors disabled:opacity-50"
>
Continue Offline
</button>
</div>
<p class="mt-4 text-xs text-gray-500 text-center">
Some features may be unavailable in offline mode.
</p>
</form>
</div>
</div>
{/if}