bistlaxmi
@bistlaxmi ·
Public
Hello, CodeSnap!
JAVASCRIPTA fresh snippet, styled and ready to share.
JAVASCRIPT
CodeSnap // free canvas
Hello, CodeSnap!
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Link, useNavigate } from "react-router-dom";
import { useToast } from "../../../hooks/useToast";
import { IoMailUnreadOutline } from "react-icons/io5";
import TextInput from "../../../components/core-ui/forms/TextInput";
import PrimaryButton from "../../../components/core-ui/buttons/PrimaryButton";
import AppLogo from "@/components/shared/AppLogo";
import TenantAuthShowcase from "@/components/shared/TenantAuthShowcase";
import { isLoggedIn } from "../../../utils/auth";
import { getTenantOrigin } from "../../../api";
import { useTenantStore } from "@/store/useTenantStore";
import { ROUTE_PATHS } from "@/allRoutes/RoutePaths";
import { forgotPasswordApiHooks } from "@/api/forgot-password/hooks";
import { normalizeApiError } from "@/api/error";
const forgotPasswordSchema = z.object({
email: z
.string()
.min(1, "Email is required")
.email("Please enter a valid email address"),
});
type ForgotPasswordSchema = z.infer<typeof forgotPasswordSchema>;
const ForgotPassword = () => {
const { showToast } = useToast();
const navigate = useNavigate();
const [serverError, setServerError] = useState<string | null>(null);
const companyName = useTenantStore(
(state) => state.tenantInfo.company_name ?? "",
);
const logoUrl = useTenantStore(
(state) => state.tenantInfo.logo_url ?? null,
);
const tenantDomain = window.location.hostname;
useEffect(() => {
if (isLoggedIn() && tenantDomain) {
window.location.replace(`${getTenantOrigin(tenantDomain)}/dashboard`);
}
}, [tenantDomain]);
const {
register,
handleSubmit,
watch,
formState: { errors, isValid },
} = useForm<ForgotPasswordSchema>({
resolver: zodResolver(forgotPasswordSchema),
mode: "onChange",
});
const watchedEmail = watch("email");
const sendOtpMutation = forgotPasswordApiHooks.useSendOtp();
const onSubmit = (data: ForgotPasswordSchema) => {
setServerError(null);
sendOtpMutation.mutate(data, {
onSuccess: () => {
showToast("success", "Verification code sent to your email!");
navigate(ROUTE_PATHS.AUTH.FORGOT_PASSWORD_OTP, {
state: { email: data.email },
replace: true,
});
},
onError: (error: unknown) => {
const msg = normalizeApiError(
error,
"Failed to send reset email!",
).message;
setServerError(msg);
showToast("error", msg);
},
});
};
return (
<div className="flex flex-col md:flex-row h-screen w-full bg-surface">
<div className="w-full md:w-1/2 flex items-center justify-center">
<div className="absolute top-6 left-6 z-20 flex items-center gap-2 text-xs font-medium tracking-wide text-text-secondary md:hidden">
<span>Powered by</span>
<AppLogo className="h-5" />
</div>
<div className="w-[90%] max-w-100 rounded-xl bg-surface p-6">
{logoUrl && (
<div className="flex justify-center mb-6 md:hidden">
<img
src={logoUrl}
alt={companyName}
className="h-10 object-contain"
/>
</div>
)}
<div className="space-y-2 mb-10">
<h2 className="text-2xl font-bold text-primary">
Forgot password?
</h2>
<p className="text-sm text-text-secondary">
Enter the email associated with your account and we'll send you a
verification code to reset your password
</p>
</div>
<form onSubmit={handleSubmit(onSubmit)} className="w-full space-y-7">
<TextInput
type="email"
placeholder="Email"
icon={<IoMailUnreadOutline size={16} />}
error={errors.email?.message}
isInvalid={!!serverError}
{...register("email")}
/>
{serverError && (
<div className="text-sm text-red -mt-4">{serverError}</div>
)}
<PrimaryButton
type="submit"
label={
sendOtpMutation.isPending
? "Sending..."
: "Send verification code"
}
disabled={!isValid || !watchedEmail || sendOtpMutation.isPending}
/>
<p className="text-center text-xs text-text-secondary">
Remember your password?{" "}
<Link
to={ROUTE_PATHS.AUTH.LOGIN}
className="text-primary font-medium hover:underline"
>
Back to login
</Link>
</p>
</form>
</div>
</div>
<div className="relative hidden w-1/2 items-center justify-center overflow-hidden bg-primary px-12 text-white md:flex xl:px-16 2xl:px-20">
<TenantAuthShowcase mode="recovery" />
</div>
</div>
);
};
export default ForgotPassword;