Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
<script lang="ts">
|
||||
import { api } from "$lib/api";
|
||||
import { apiFormError, fieldDescribedBy, fieldInvalid } from "$lib/api-form-error";
|
||||
import { i18n } from "$lib/i18n";
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label
|
||||
} from "$lib/components/ui";
|
||||
|
||||
let email = $state("");
|
||||
let error = $state("");
|
||||
let fieldErrors = $state<Record<string, string>>({});
|
||||
let loading = $state(false);
|
||||
let sent = $state(false);
|
||||
const errorId = "forgot-password-form-error";
|
||||
|
||||
async function onSubmit(event: Event) {
|
||||
event.preventDefault();
|
||||
error = "";
|
||||
fieldErrors = {};
|
||||
loading = true;
|
||||
try {
|
||||
await api("/api/auth/forgot-password", {
|
||||
method: "POST",
|
||||
body: { email }
|
||||
});
|
||||
sent = true;
|
||||
} catch (err) {
|
||||
const result = apiFormError(err, i18n.t("auth.forgot.failed"), {
|
||||
email: ["email", "required", "rate limit"]
|
||||
});
|
||||
error = result.message;
|
||||
fieldErrors = result.fields;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle level={1}>{i18n.t("auth.forgot.title")}</CardTitle>
|
||||
<CardDescription>{i18n.t("auth.forgot.description")}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{#if sent}
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
<strong class="text-text">{i18n.t("auth.forgot.sentTitle")}</strong>
|
||||
{" "}
|
||||
{i18n.t("auth.forgot.sentBody")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<p class="mt-5 text-center text-sm text-text-muted">
|
||||
<a href="/login" class="font-medium text-link hover:underline">{i18n.t("auth.forgot.backToSignIn")}</a>
|
||||
</p>
|
||||
{:else}
|
||||
<form
|
||||
method="dialog"
|
||||
class="space-y-4"
|
||||
onsubmit={onSubmit}
|
||||
aria-busy={loading}
|
||||
aria-describedby={error ? errorId : undefined}
|
||||
>
|
||||
{#if error}
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription id={errorId}>{error}</AlertDescription>
|
||||
</Alert>
|
||||
{/if}
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="email">{i18n.t("common.email")}</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
name="email"
|
||||
autocomplete="email"
|
||||
required
|
||||
aria-invalid={fieldInvalid(fieldErrors, "email") ??
|
||||
(error && !Object.keys(fieldErrors).length ? "true" : undefined)}
|
||||
aria-describedby={fieldDescribedBy(fieldErrors, "email", errorId) ??
|
||||
(error && !Object.keys(fieldErrors).length ? errorId : undefined)}
|
||||
bind:value={email}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button type="submit" class="w-full shadow-sm" loading={loading}>
|
||||
{loading ? i18n.t("auth.forgot.submitting") : i18n.t("auth.forgot.submit")}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<p class="mt-5 text-center text-sm text-text-muted">
|
||||
<a href="/login" class="font-medium text-link hover:underline">{i18n.t("auth.forgot.backToSignIn")}</a>
|
||||
</p>
|
||||
{/if}
|
||||
</CardContent>
|
||||
</Card>
|
||||
Reference in New Issue
Block a user