Files
mule-image/web/src/routes/login/+page.svelte
dtoro 24dfa996b3 web: de-brand PhotoPrism references in user-facing copy
Replaces "PhotoPrism" in UI strings (empty states, tooltips, toasts,
log header, login screen) with neutral terms like "the indexer", "the
library", "the server" — accurate regardless of backend. The login
header becomes "Mulimage" and drops the explicit PhotoPrism mention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 08:26:12 +02:00

118 lines
3.5 KiB
Svelte

<script lang="ts">
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { toast } from 'svelte-sonner';
import { getConfig, login } from '$lib/services/photoprism';
import { isAuthenticated } from '$lib/stores/session.svelte';
let username = $state('');
let password = $state('');
let submitting = $state(false);
// OIDC config probed lazily from /api/v1/config (no auth required).
// Empty when OIDC is dormant; populated triggers the SSO button.
let oidc = $state<{ provider: string; loginUri: string } | null>(null);
$effect(() => {
if (isAuthenticated()) {
void goto('/', { replaceState: true });
}
});
onMount(async () => {
try {
const cfg = await getConfig();
const ext = cfg.ext?.oidc;
if (ext?.enabled && ext.loginUri) {
oidc = { provider: ext.provider || 'OIDC', loginUri: ext.loginUri };
}
} catch {
// /config is best-effort — SSO button just stays hidden.
}
});
async function onSubmit(e: SubmitEvent) {
e.preventDefault();
if (submitting) return;
submitting = true;
try {
await login(username, password);
toast.success('Signed in');
await goto('/', { replaceState: true });
} catch (err) {
const msg = err instanceof Error ? err.message : 'Login failed';
toast.error(msg);
} finally {
submitting = false;
}
}
function onOidcClick() {
if (!oidc) return;
// Full reload so PhotoPrism handles the redirect + cookie set on
// its own; the SPA picks up the resulting session on return via
// bootstrapSessionFromCookies in the root layout.
window.location.href = oidc.loginUri;
}
</script>
<div class="flex min-h-screen items-center justify-center bg-background p-6">
<form
onsubmit={onSubmit}
class="w-full max-w-sm space-y-5 rounded-lg border border-border bg-card p-8 shadow-sm"
>
<header class="space-y-1">
<h1 class="text-2xl font-semibold tracking-tight text-foreground">Mulimage</h1>
<p class="text-sm text-muted-foreground">Sign in to your account.</p>
</header>
<label class="block space-y-1.5">
<span class="text-sm font-medium text-foreground">Username</span>
<input
type="text"
autocomplete="username"
required
bind:value={username}
class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring"
/>
</label>
<label class="block space-y-1.5">
<span class="text-sm font-medium text-foreground">Password</span>
<input
type="password"
autocomplete="current-password"
required
bind:value={password}
class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring"
/>
</label>
<button
type="submit"
disabled={submitting}
class="w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow-sm hover:opacity-90 disabled:opacity-50"
>
{submitting ? 'Signing in…' : 'Sign in'}
</button>
{#if oidc}
<div class="relative py-1">
<div class="absolute inset-0 flex items-center" aria-hidden="true">
<div class="w-full border-t border-border"></div>
</div>
<div class="relative flex justify-center">
<span class="bg-card px-2 text-xs text-muted-foreground">or</span>
</div>
</div>
<button
type="button"
onclick={onOidcClick}
class="w-full rounded-md border border-input bg-background px-4 py-2 text-sm font-medium text-foreground shadow-sm hover:bg-muted"
>
Sign in with {oidc.provider}
</button>
{/if}
</form>
</div>