import { useForm } from "@tanstack/react-form";
import { Link, createFileRoute } from "@tanstack/react-router";
import {
AuthPageCard,
AuthPageShell,
authRedirectSearchSchema,
} from "@/client/features/auth/AuthPage";
import { getFieldError, getFormError } from "@/client/lib/forms";
import { authClient } from "@/lib/auth-client";
import { isHostedClientAuthMode } from "@/lib/auth-mode";
import { getSignInSearch, normalizeAuthRedirect } from "@/lib/auth-redirect";
import {
HOSTED_PASSWORD_MAX_LENGTH,
HOSTED_PASSWORD_MIN_LENGTH,
} from "@/lib/auth-options";
import { z } from "zod";
const resetPasswordSchema = z
.object({
password: z
.string()
.min(
HOSTED_PASSWORD_MIN_LENGTH,
`Password must be at least ${HOSTED_PASSWORD_MIN_LENGTH} characters.`,
)
.max(
HOSTED_PASSWORD_MAX_LENGTH,
`Password must be at most ${HOSTED_PASSWORD_MAX_LENGTH} characters.`,
),
confirmPassword: z.string(),
})
.refine((value) => value.password === value.confirmPassword, {
message: "Passwords do not match.",
path: ["confirmPassword"],
});
const resetPasswordSearchSchema = authRedirectSearchSchema.extend({
error: z.string().optional(),
token: z.string().optional(),
});
export const Route = createFileRoute("/reset-password")({
validateSearch: resetPasswordSearchSchema,
component: ResetPasswordPage,
});
function getResetPasswordErrorMessage(error: string | undefined) {
switch ((error ?? "").toLowerCase()) {
case "invalid_token":
return "This reset link is no longer valid. Request a new one to keep going.";
case "token_expired":
return "This reset link has expired. Request a new one to keep going.";
default:
return error
? "This reset link can't be used anymore. Request a new one and try again."
: null;
}
}
function getResetPasswordPageCopy({
isHostedMode,
isComplete,
routeError,
hasToken,
}: {
isHostedMode: boolean;
isComplete: boolean;
routeError: string | null;
hasToken: boolean;
}) {
if (!isHostedMode) {
return {
title: "Reset password",
helperText: "Password reset isn't available right now.",
};
}
if (isComplete) {
return {
title: "Password updated",
helperText:
"Your password has been updated. Sign in with your new password.",
};
}
if (routeError || !hasToken) {
return {
title: "Reset link expired",
helperText:
routeError ||
"This reset link is no longer valid. Request a new one to keep going.",
};
}
return {
title: "Reset password",
helperText: "Choose a new password for your account.",
};
}
function ResetPasswordPage() {
const search = Route.useSearch();
const redirectTo = normalizeAuthRedirect(search.redirect);
const isHostedMode = isHostedClientAuthMode();
const routeError = getResetPasswordErrorMessage(search.error);
const token = typeof search.token === "string" ? search.token : null;
const form = useForm({
defaultValues: {
password: "",
confirmPassword: "",
},
validators: {
onSubmit: resetPasswordSchema,
},
onSubmit: async ({ formApi, value }) => {
if (!token) {
formApi.setErrorMap({
onSubmit: {
form: "This reset link is no longer valid. Request a new one and try again.",
fields: {},
},
});
return;
}
try {
const result = await authClient.resetPassword({
newPassword: value.password,
token,
});
if (result.error) {
formApi.setErrorMap({
onSubmit: {
form: "This reset link is no longer valid. Request a new one and try again.",
fields: {},
},
});
return;
}
} catch {
formApi.setErrorMap({
onSubmit: {
form: "We couldn't update your password right now. Please try again.",
fields: {},
},
});
}
},
});
return (
{error}
) : null}{error}
) : null}{errorMessage}
) : null} )} ); }} ); }