add some features

This commit is contained in:
2026-06-27 23:47:15 +02:00
parent 5b44a65e51
commit 433085a895
6 changed files with 623 additions and 18 deletions
+198
View File
@@ -0,0 +1,198 @@
<script lang="ts">
import {
AlertCircle,
Download,
Eye,
EyeOff,
Key,
LoaderCircle,
Lock,
Upload,
User,
X,
} from "@lucide/svelte";
interface Props {
action: "push" | "pull";
error: string;
isBusy: boolean;
onSubmit: (username: string, password: string, save: boolean) => void;
onCancel: () => void;
}
let {
action,
error = "",
isBusy = false,
onSubmit,
onCancel,
}: Props = $props();
type Mode = "credentials" | "token";
let mode = $state<Mode>("credentials");
let username = $state("");
let password = $state("");
let showPassword = $state(false);
let saveSession = $state(false);
let canSubmit = $derived(
!isBusy &&
password.trim().length > 0 &&
(mode === "token" || username.trim().length > 0),
);
function handleSubmit(e: SubmitEvent) {
e.preventDefault();
if (!canSubmit) return;
onSubmit(mode === "token" ? "oauth2" : username, password, saveSession);
}
</script>
<div
class="dialog-backdrop"
role="presentation"
onclick={(e) => { if (e.target === e.currentTarget) onCancel(); }}
>
<div class="cred-card" role="dialog" aria-modal="true" aria-label="Git-Zugangsdaten" tabindex="-1">
<!-- Hero header -->
<div class="cred-hero">
<div class="cred-hero-icon">
{#if action === "push"}
<Upload size={28} aria-hidden="true" />
{:else}
<Download size={28} aria-hidden="true" />
{/if}
</div>
<div class="cred-hero-text">
<p class="cred-hero-label">{action === "push" ? "Push" : "Pull"}</p>
<h2 class="cred-hero-title">Zugangsdaten erforderlich</h2>
</div>
<button class="cred-close" type="button" onclick={onCancel} title="Abbrechen" aria-label="Abbrechen">
<X size={16} aria-hidden="true" />
</button>
</div>
<!-- Form -->
<form class="cred-body" onsubmit={handleSubmit}>
<!-- Mode segmented control -->
<div class="cred-segment" role="group" aria-label="Authentifizierungsart">
<button
type="button"
class="cred-seg-btn"
class:active={mode === "credentials"}
onclick={() => { mode = "credentials"; }}
aria-pressed={mode === "credentials"}
>
<User size={13} aria-hidden="true" />
Username & Passwort
</button>
<button
type="button"
class="cred-seg-btn"
class:active={mode === "token"}
onclick={() => { mode = "token"; }}
aria-pressed={mode === "token"}
>
<Key size={13} aria-hidden="true" />
Token
</button>
</div>
<!-- Fields -->
<div class="cred-fields">
{#if mode === "credentials"}
<div class="cred-field">
<label class="cred-field-label" for="cred-username">Username</label>
<div class="cred-input">
<User size={15} class="cred-field-icon" aria-hidden="true" />
<input
id="cred-username"
type="text"
bind:value={username}
placeholder="z. B. mein-github-username"
autocomplete="username"
disabled={isBusy}
/>
</div>
</div>
{/if}
<div class="cred-field">
<label class="cred-field-label" for="cred-password">
{mode === "token" ? "Token" : "Passwort"}
</label>
<div class="cred-input">
<Lock size={15} class="cred-field-icon" aria-hidden="true" />
<input
id="cred-password"
type={showPassword ? "text" : "password"}
bind:value={password}
placeholder={mode === "token"
? "ghp_… oder anderer Zugangstoken"
: "Passwort oder Personal Access Token"}
autocomplete="current-password"
disabled={isBusy}
/>
<button
type="button"
class="cred-reveal"
onclick={() => { showPassword = !showPassword; }}
tabindex="-1"
aria-label={showPassword ? "Verbergen" : "Anzeigen"}
>
{#if showPassword}
<EyeOff size={14} aria-hidden="true" />
{:else}
<Eye size={14} aria-hidden="true" />
{/if}
</button>
</div>
</div>
</div>
<!-- Token hint -->
{#if mode === "token"}
<div class="cred-token-hint">
<Key size={13} aria-hidden="true" />
<span>Username wird automatisch auf <code>oauth2</code> gesetzt — funktioniert mit GitHub, GitLab & Bitbucket.</span>
</div>
{/if}
<!-- Error banner -->
{#if error}
<div class="cred-error" role="alert">
<AlertCircle size={14} aria-hidden="true" />
<span>{error}</span>
</div>
{/if}
<!-- Footer -->
<div class="cred-footer">
<label class="cred-save">
<input type="checkbox" bind:checked={saveSession} disabled={isBusy} />
<span>Für diese Sitzung merken</span>
</label>
<div class="cred-btns">
<button type="button" class="cred-cancel" onclick={onCancel} disabled={isBusy}>
Abbrechen
</button>
<button class="cred-submit" type="submit" disabled={!canSubmit}>
{#if isBusy}
<LoaderCircle class="spin" size={15} aria-hidden="true" />
{:else if action === "push"}
<Upload size={15} aria-hidden="true" />
{:else}
<Download size={15} aria-hidden="true" />
{/if}
{action === "push" ? "Push" : "Pull"}
</button>
</div>
</div>
</form>
</div>
</div>