Files
gerbeur/src/pages/ResetPassword.tsx
khannurien c8c7b05c25
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 41s
v3: fixed inconsistent page titles
2026-06-28 06:51:42 +00:00

141 lines
3.8 KiB
TypeScript

import { useState } from "react";
import { Link, useNavigate, useSearchParams } from "react-router";
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { API_URL, VALIDATION } from "../config/api.ts";
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
import { PageShell } from "../components/PageShell.tsx";
import {
expectOk,
FormError,
FormProvider,
SubmitButton,
TextField,
useApiForm,
} from "../components/form/index.ts";
interface Values {
newPassword: string;
confirm: string;
}
export function ResetPassword() {
useDocumentTitle(t`Reset password`);
const [params] = useSearchParams();
const navigate = useNavigate();
const token = params.get("token") ?? "";
const [done, setDone] = useState(false);
const form = useApiForm<Values>({
mode: "onChange",
defaultValues: { newPassword: "", confirm: "" },
});
const onSubmit = form.submit(async ({ newPassword }) => {
await expectOk(
await fetch(`${API_URL}/api/users/reset-password`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token, newPassword }),
}),
);
setDone(true);
});
if (!token) {
return (
<PageShell centered>
<div className="auth-card">
<h1 className="auth-card-title">
<Trans>Invalid link</Trans>
</h1>
<p>
<Trans>This reset link is missing or malformed.</Trans>
</p>
<Link
to="/login"
className="btn-primary"
style={{ marginTop: "1rem", display: "inline-block" }}
>
<Trans>Back to login</Trans>
</Link>
</div>
</PageShell>
);
}
if (done) {
return (
<PageShell centered>
<div className="auth-card">
<h1 className="auth-card-title">
<Trans>Password updated</Trans>
</h1>
<p style={{ marginBottom: "1rem" }}>
<Trans>Your password has been changed. You can now log in.</Trans>
</p>
<button
type="button"
className="btn-primary"
onClick={() => navigate("/login")}
>
<Trans>Go to login</Trans>
</button>
</div>
</PageShell>
);
}
return (
<PageShell centered>
<div className="auth-card">
<h1 className="auth-card-title">
<Trans>Set new password</Trans>
</h1>
<FormProvider {...form}>
<form onSubmit={onSubmit} className="form">
<FormError title={t`Reset failed`} />
<TextField<Values>
name="newPassword"
type="password"
placeholder={t`New password`}
autoComplete="new-password"
autoFocus
maxLength={VALIDATION.PASSWORD_MAX}
rules={{
required: true,
minLength: {
value: VALIDATION.PASSWORD_MIN,
message: t`At least ${VALIDATION.PASSWORD_MIN} characters`,
},
}}
/>
<TextField<Values>
name="confirm"
type="password"
placeholder={t`Confirm new password`}
autoComplete="new-password"
rules={{
required: true,
validate: (value, values) =>
value === values.newPassword || t`Passwords do not match`,
}}
/>
<SubmitButton pendingLabel={<Trans>Saving…</Trans>}>
<Trans>Set new password</Trans>
</SubmitButton>
</form>
</FormProvider>
<p className="auth-card-footer">
<Link to="/login">
<Trans>Back to login</Trans>
</Link>
</p>
</div>
</PageShell>
);
}