169 lines
5.5 KiB
Svelte
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}
|