Fresh code drop

Hello, CodeSnap!

JAVASCRIPT

A 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;

Turn your code into a post.Five templates are free—no account needed.

Remix this snap