Files
Moku/src/lib/components/chrome/AuthGate.svelte
T
2026-05-24 20:31:46 -05:00

78 lines
4.2 KiB
Svelte

<script lang="ts">
import logoUrl from '$lib/assets/moku-icon-splash.svg'
import { appState } from '$lib/state/app.svelte'
import { boot, submitLogin, bypassBoot } from '$lib/state/boot.svelte'
function handleBypass() {
bypassBoot(appState.authMode, boot.loginUser)
}
</script>
{#if appState.status === 'auth'}
<div class="overlay">
<div class="card anim-scale-in">
<img src={logoUrl} alt="Moku" class="logo" />
<p class="title">moku</p>
<span class="mode-badge">
{appState.authMode === 'UI_LOGIN' ? 'UI Login' : 'Basic Auth'}
</span>
<p class="host">{appState.serverUrl || 'localhost:4567'}</p>
{#if boot.loginError}
<p class="error">{boot.loginError}</p>
{/if}
<div class="fields">
<input
class="input"
type="text"
placeholder="Username"
bind:value={boot.loginUser}
disabled={boot.loginBusy}
autocomplete="username"
onkeydown={(e) => e.key === 'Enter' && submitLogin()}
/>
<input
class="input"
type="password"
placeholder="Password"
bind:value={boot.loginPass}
disabled={boot.loginBusy}
autocomplete="current-password"
onkeydown={(e) => e.key === 'Enter' && submitLogin()}
/>
</div>
<button
class="btn"
onclick={submitLogin}
disabled={boot.loginBusy || !boot.loginUser.trim() || !boot.loginPass.trim()}
>
{boot.loginBusy ? 'Signing in…' : 'Sign in'}
</button>
<button class="btn btn--ghost" onclick={handleBypass}>Skip</button>
</div>
</div>
{/if}
<style>
.overlay { position:fixed; inset:0; z-index:10000; display:flex; align-items:center; justify-content:center; pointer-events:none; }
.card { pointer-events:auto; width:min(280px, calc(100vw - 48px)); background:var(--bg-surface); border:1px solid var(--border-base); border-radius:var(--radius-xl); padding:var(--sp-6) var(--sp-5); display:flex; flex-direction:column; align-items:center; gap:var(--sp-3); box-shadow:0 32px 80px rgba(0,0,0,0.75); text-align:center; }
.logo { width:56px; height:56px; border-radius:14px; display:block; }
.title { font-family:var(--font-ui); font-size:11px; font-weight:500; letter-spacing:0.26em; text-transform:uppercase; color:var(--text-secondary); margin:-6px 0 0; user-select:none; }
.mode-badge { font-family:var(--font-ui); font-size:var(--text-2xs); letter-spacing:var(--tracking-wider); text-transform:uppercase; color:var(--accent-fg); background:var(--accent-muted); border:1px solid var(--accent-dim); border-radius:var(--radius-full); padding:2px 10px; }
.host { font-family:var(--font-ui); font-size:var(--text-xs); color:var(--text-faint); letter-spacing:var(--tracking-wide); margin:-4px 0 0; }
.error { font-family:var(--font-ui); font-size:var(--text-xs); color:var(--color-error); background:var(--color-error-bg); border:1px solid var(--color-error); border-radius:var(--radius-sm); padding:var(--sp-2) var(--sp-3); width:100%; box-sizing:border-box; }
.fields { display:flex; flex-direction:column; gap:var(--sp-2); width:100%; }
.input { width:100%; background:var(--bg-raised); border:1px solid var(--border-strong); border-radius:var(--radius-md); padding:8px 12px; font-size:var(--text-sm); color:var(--text-primary); outline:none; box-sizing:border-box; transition:border-color var(--t-base), box-shadow var(--t-base); font-family:inherit; }
.input:focus { border-color:var(--border-focus); box-shadow:0 0 0 2px color-mix(in srgb, var(--accent) 20%, transparent); }
.input:disabled { opacity:0.5; }
.btn { width:100%; padding:9px; border-radius:var(--radius-md); background:var(--accent); border:1px solid var(--accent); color:var(--accent-fg); font-size:var(--text-sm); font-family:var(--font-ui); letter-spacing:var(--tracking-wide); cursor:pointer; transition:opacity var(--t-base); }
.btn:hover:not(:disabled) { opacity:0.85; }
.btn:disabled { opacity:0.35; cursor:default; }
.btn--ghost { background:none; border-color:transparent; color:var(--text-faint); font-size:var(--text-xs); padding:4px; }
.btn--ghost:hover:not(:disabled) { color:var(--text-muted); opacity:1; }
</style>