First working POC
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
<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);
|
||||
|
||||
const session = auth.getCurrentSession();
|
||||
const username = session?.username ?? "User";
|
||||
const 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() {
|
||||
auth.dismissReauth();
|
||||
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}
|
||||
Reference in New Issue
Block a user