Jonker
Jonker

Reputation: 1

Add "Listener" to client component (Navbar) when server action fires

Hello good people and fellow developers.

I have a problem that I am facing at the moment. I have a Navbar component that is called in my root Layout folder right above the {children} so that it is available across the entire application.

When no user is signed in then the sign up and sign in buttons are obviously available but my problem is that when a user signs up or in then the Navbar does not update because it is not a path so I can't use revalidateParth in the register action or login action.

How can I put an active "listener" on my navbar to update when a user is actually logged in.

Here is the code to reverence:

"use client";
import { CurrentUser } from "@/lib/services/models/CurrentUser.model";
import { getCurrentUser } from "@/lib/services/user.service";
import Link from "next/link";
import { useEffect, useState } from "react";
import RegisterUser from "../Registration/Registration";
import ToggleTheme from "../Theme/ToggleTheme";
import AvatarDropdownMenu from "./AvatarDropdownMenu";
import LoginUserComponent from "../Login/Login";
import Image from "next/image";
import logo from "../../../public/ForexRevolution.svg";
import { cn } from "@/lib/utils";
import { buttonVariants } from "../ui/button";
import { revalidateTag } from "next/cache";

const Navbar = () => {
  const [user, setUser] = useState<CurrentUser | null>(null);
  const [scrolled, setScrolled] = useState(false);

  useEffect(() => {
    const getUsers = async () => {
      try {
        const users = await getCurrentUser();
        if (users) {
          setUser(users);
        }
      } catch (error) {
        console.error("Error fetching user details:", error);
      }
    };

    getUsers();
  }, []);

  useEffect(() => {
    const handleScroll = () => {
      const isScrolled = window.scrollY > 0;
      setScrolled(isScrolled);
    };

    // Add event listener
    window.addEventListener("scroll", handleScroll);

    // Clean up
    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, []);

  return (
    <nav
      className={`flex z-50 sticky top-0 justify-between items-center lg:px-20 md:px-10 px-5 py-2.5 lg:h-20 md:h-16 h-12 border-b transition-colors duration-700 ${
        scrolled
          ? "bg-white border-blue-600"
          : "border-gray-200 dark:border-blue-200 dark:bg-white dark:text-black bg-white"
      }`}
    >
      <div>
        {user ? (
          <>
            <Link href={"/dashboard"}>
              <Image src={logo} alt="Logo" width={80} />
            </Link>
          </>
        ) : (
          <>
            <Link href={"/"}>
              <Image src={logo} alt="Logo" width={80} />
            </Link>
          </>
        )}
      </div>

      <div className="flex justify-center items-center gap-5">
        {user ? (
          <div className="flex items-center justify-center gap-5">
            <p className="text-sm font-bold hidden md:block lg:block">
              {user.UserName}
            </p>
            <div className="md:hidden lg:hidden block">
              <AvatarDropdownMenu userName={user.UserName} />
            </div>
            <div className="hidden md:block lg:block">
              <AvatarDropdownMenu />
            </div>
          </div>
        ) : (
          <div className="flex items-center justify-center gape-10">
            <RegisterUser />
            <span className="h-6 w-px bg-gray-200" aria-hidden="true" />
            <LoginUserComponent />
            <span className="h-6 w-px bg-gray-200 md:block lg:block hidden" />
            <Link
              className={`${cn(
                buttonVariants({ variant: "default" })
              )} md:block lg:block hidden`}
              href={"/course"}
            >
              Free Resources
            </Link>
          </div>
        )}
        {/* <div className="mb:block lg:block hidden">
          <ToggleTheme />
        </div> */}
      </div>
    </nav>
  );
};

export default Navbar;

and the server actions:

// Action to register a new user
export async function registerUserAction(
  prevState: UserActionState | null,
  data: FormData
): Promise<UserActionState> {
  try {
    const { firstName, lastName, userName, email, password } =
      registerUserSchema.parse(data);

    // 1. Does the email already exist?
    if (await db.emailExists(email)) {
      return {
        status: "error",
        errors: [
          {
            path: "email",
            message: "Email address is already registered",
          },
        ],
      };
    }

    // 2. UserName unique
    if (await db.usernameExists(userName)) {
      return {
        status: "error",
        errors: [
          {
            path: "userName",
            message: "Username is already registered",
          },
        ],
      };
    }

    const hashedPassword = await hashPassword(password);
    const expireDate = getTokenExpiryDate();
    const token = generateToken();
    const verificationToken = generateToken();

    if (
      !(await db.createUser(
        email,
        firstName,
        lastName,
        hashedPassword,
        userName,
        token,
        expireDate,
        verificationToken,
        "noob"
      ))
    ) {
      return { message: "Failed to register user", status: "error" };
    }

    // Only set cookie if prismaClient call was successful
    setTokenCookie(token, expireDate);
    await sendVerificationEmail(email, userName, verificationToken);
    return { message: "Successfully registered user", status: "success" };
  } catch (e) {
    // In case of a ZodError (caused by our validation) we're adding issues to our response
    if (e instanceof ZodError) {
      return {
        status: "error",
        message: "Invalid form data",
        errors: e.issues.map((issue) => ({
          path: issue.path.join("."),
          message: `${issue.message}`,
        })),
      };
    }
    return {
      status: "error",
      message: "Something went wrong. Please try again.",
    };
  }
}

// Action to log the user in
export async function logInAction(
  prevState: UserActionState | null,
  data: FormData
): Promise<UserActionState> {
  try {
    const { email, password } = loginSchema.parse(data);
    const user = await db.getUserByEmail(email);

    if (user === null) {
      return {
        status: "error",
        message: "Invalid form data",
        errors: [
          {
            path: "email",
            message: "Invalid email address or password",
          },
        ],
      };
    }

    if (!(await verifyPassword(password, user.Password))) {
      return {
        status: "error",
        errors: [
          {
            path: "email",
            message: "Invalid email address or password",
          },
        ],
      };
    }

    const expireDate = getTokenExpiryDate();
    const token = generateToken();

    if (!(await db.setAuthTokenByUserId(user.Id, token, expireDate))) {
      return {
        status: "error",
        message: "Unable to log in, please try again later",
      };
    }
    // Set token only after DB calls done
    setTokenCookie(token, expireDate);
    return { message: "Successfully registered user", status: "success" };
  } catch (e) {
    // In case of a ZodError (caused by our validation) we're adding issues to our response
    if (e instanceof ZodError) {
      return {
        status: "error",
        message: "Invalid form data",
        errors: e.issues.map((issue) => ({
          path: issue.path.join("."),
          message: `${issue.message}`,
        })),
      };
    }
    return {
      status: "error",
      message: "Something went wrong. Please try again.",
    };
  }
}

I am trying to update the navbar without having to hard reload the page.

Upvotes: 0

Views: 23

Answers (0)

Related Questions