Files
probo/apps/console/src/pages/iam/auth/ResetPasswordPage.tsx
Émile Ré f98b73f073 Upgrade frontend toolchain to latest majors
Bump React 19.2, Relay 21, React Router 8, Vite 8 with
@vitejs/plugin-react 6, ESLint 10, GraphQL 17, TypeScript 6,
@types/node 24, and Tailwind 4.3 across the workspaces.

vite-plugin-react 6 (Vite 8) no longer runs Babel, so the Relay
tagged-template transform now runs through @rolldown/plugin-babel
in the console and trust Vite configs.

Relay 21 ships first-party types and enables the ambiguous-alias
check by default; disable that flag to preserve existing queries
and add explicit usePreloadedQuery type arguments where the new
types no longer infer the operation. TypeScript 6 deprecations and
stricter inference are addressed in tsconfigs and call sites.

Keep n8n-node on ESLint 9 and eslint-plugin-react on 7.37.5, the
newest releases compatible with their toolchains.

Signed-off-by: Émile Ré <emile@probo.com>
2026-06-19 13:51:17 +02:00

161 lines
4.7 KiB
TypeScript

// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import { formatError } from "@probo/helpers";
import { usePageTitle } from "@probo/hooks";
import { useTranslate } from "@probo/i18n";
import { Button, Field, useToast } from "@probo/ui";
import { useMutation } from "react-relay";
import { Link, useNavigate, useSearchParams } from "react-router";
import { graphql } from "relay-runtime";
import { z } from "zod";
import type { ResetPasswordPageMutation } from "#/__generated__/iam/ResetPasswordPageMutation.graphql";
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
const resetPasswordMutation = graphql`
mutation ResetPasswordPageMutation($input: ResetPasswordInput!) {
resetPassword(input: $input) {
success
}
}
`;
const schema = z
.object({
password: z.string().min(8),
confirmPassword: z.string().min(8),
})
.refine(data => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ["confirmPassword"],
});
export default function ResetPasswordPage() {
const { __ } = useTranslate();
const { toast } = useToast();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const token = searchParams.get("token");
usePageTitle(__("Reset password"));
const { register, handleSubmit, formState } = useFormWithSchema(schema, {
defaultValues: {
password: "",
confirmPassword: "",
},
});
const [resetPassword] = useMutation<ResetPasswordPageMutation>(
resetPasswordMutation,
);
const onSubmit = handleSubmit((data) => {
if (!token) {
toast({
title: __("Reset failed"),
description: __("Invalid or missing reset token"),
variant: "error",
});
return;
}
resetPassword({
variables: {
input: {
password: data.password,
token,
},
},
onError: (e: Error) => {
toast({
title: __("Reset failed"),
description: e.message,
variant: "error",
});
},
onCompleted: (_, e) => {
if (e) {
toast({
title: __("Reset failed"),
description: formatError(
__("Password reset failed"),
e,
),
variant: "error",
});
return;
}
toast({
title: __("Success"),
description: __("Password reset successfully"),
variant: "success",
});
void navigate("/auth/login", { replace: true });
},
});
});
return (
<div className="space-y-6 w-full max-w-md mx-auto pt-8">
<div className="space-y-2 text-center">
<h1 className="text-3xl font-bold">{__("Reset password")}</h1>
<p className="text-txt-tertiary">
{__("Enter your new password to reset your account")}
</p>
</div>
<form onSubmit={e => void onSubmit(e)} className="space-y-4">
<Field
label={__("New Password")}
type="password"
placeholder="••••••••"
{...register("password")}
required
error={formState.errors.password?.message}
/>
<Field
label={__("Confirm Password")}
type="password"
placeholder="••••••••"
{...register("confirmPassword")}
required
error={formState.errors.confirmPassword?.message}
/>
<Button type="submit" className="w-xs h-10 mx-auto mt-6" disabled={formState.isLoading}>
{formState.isLoading
? __("Resetting password...")
: __("Reset password")}
</Button>
</form>
<div className="text-center">
<p className="text-sm text-txt-tertiary">
{__("Remember your password?")}
{" "}
<Link
to="/auth/login"
className="underline text-txt-primary hover:text-txt-secondary"
>
{__("Log in here")}
</Link>
</p>
</div>
</div>
);
}