TMP: GClient Learner Platform – Phase 1: Building a Dynamic Navbar with Next.js, TypeScript, and Tailwind CSS (Update 3)

Let's dive right into the code : "use client"; import { useState, useEffect, useRef } from "react"; import LoginClient from "../LearnerRegFlow/LoginClient"; import SignupClient from "../LearnerRegFlow/SignupClient"; import ResetPassword from "../LearnerRegFlow/ResetPassword"; import Link from "next/link"; import Image from "next/image"; import { LogIn, ChevronDown } from "lucide-react"; const Navbar = () => { const [showLogin, setShowLogin] = useState(false); const [showSignup, setShowSignup] = useState(false); const [showResetPassword, setShowResetPassword] = useState(false); const [user, setUser] = useState(null); const [dropdownOpen, setDropdownOpen] = useState(false); useEffect(() => { // Load user from localStorage const storedUser = localStorage.getItem("user"); if (storedUser) { setUser(JSON.parse(storedUser)); } }, []); const handleCloseModals = () => { setShowLogin(false); setShowSignup(false); setShowResetPassword(false); }; const handleForgotPassword = () => { setShowLogin(false); setShowSignup(false); setShowResetPassword(true); }; const handleSwitchToSignup = () => { setShowLogin(false); setShowResetPassword(false); setShowSignup(true); }; const handleSwitchToLogin = () => { setShowSignup(false); setShowResetPassword(false); setShowLogin(true); }; const handleLoginSuccess = (userData: { name: string; email: string }) => { setUser(userData); }; const handleLogout = () => { localStorage.removeItem("token"); localStorage.removeItem("user"); setUser(null); setDropdownOpen(false); }; return ( Home Courses {user ? ( // Logged-in UI (Profile Dropdown) setDropdownOpen(!dropdownOpen)}> {user?.name ? user.name .split(" ") // Split by space into words .map((word) => word.charAt(0).toUpperCase()) // Get first letter of each word .slice(0, 2) // Take only first two words (if available) .join("") // Combine letters : "U"} {user.name} {/* Dropdown Menu */} {dropdownOpen && ( Portal Logout )} ) : ( // Login Button setShowLogin(true)} > Login )} {/* LOGIN / SIGNUP / RESET PASSWORD MODALS */} {showLogin || showSignup || showResetPassword ? ( {showLogin && ( )} {showSignup && ( )} {showResetPassword && ( )} ) : null} ); }; export default Navbar; I know this is a lot to wrap your head around but stay with me or it's not maybe it's part of the day-to-day life of a developer to go through this amount of code. Well, this is my thinking process when building the Navbar components. This is the relative path to the component src\app\components\LearnerPage\Navbar.tsx. you can get my folder structure for my project in the previous post, ie Update 2 This file defines a navigation bar component for my Next.js application, handling user authentication and navigation. Here's a detailed explanation: Navbar Component 1. "use client"; This directive at the beginning of the file indicates that this component is a client-side component. In Next.js, components are server-side rendered by default. "use client" specifies that this component, and any components imported into it, will be rendered in the user's browser. This is necessary because this component uses React hooks like useState and useEffect, which are client-side functionalities. Server-Side Rendering (SSR): The server sends a complete web page to your browser, so it loads quickly and is easier for search engines to understand. Client-Side Rendering (CSR): The server sends basic instructions to your browser, which then builds the web page itself. This can make the first load slower, but moving between pages can be faster after that. React hooks (e.g., useState, useEffect) are tools that manage things like data changes and side effects, and they work only in the browser environment. 2. Import Statements import { useState, useEffect, useRef } from "react"; import LoginClient from "../LearnerRegFlow/LoginClient"; import SignupClient from "../LearnerRegFlow/SignupClient"; import ResetPassword from "../LearnerRegFlow/ResetPassword"; import Link from "next/link"; import Image from "next/image"; import { LogIn, ChevronDown } from "lucide-react"; typescript useState, useEffect

Feb 15, 2025 - 21:05
 0
TMP: GClient Learner Platform – Phase 1: Building a Dynamic Navbar with Next.js, TypeScript, and Tailwind CSS (Update 3)

Let's dive right into the code :



"use client";


import { useState, useEffect, useRef } from "react";
import LoginClient from "../LearnerRegFlow/LoginClient";
import SignupClient from "../LearnerRegFlow/SignupClient";
import ResetPassword from "../LearnerRegFlow/ResetPassword";
import Link from "next/link";
import Image from "next/image";
import { LogIn, ChevronDown } from "lucide-react";


const Navbar = () => {
  const [showLogin, setShowLogin] = useState(false);
  const [showSignup, setShowSignup] = useState(false);
  const [showResetPassword, setShowResetPassword] = useState(false);
  const [user, setUser] = useState<{ name: string; email: string } | null>(null);
  const [dropdownOpen, setDropdownOpen] = useState(false);


  useEffect(() => {
    // Load user from localStorage
    const storedUser = localStorage.getItem("user");
    if (storedUser) {
      setUser(JSON.parse(storedUser));
    }
  }, []);


  const handleCloseModals = () => {
    setShowLogin(false);
    setShowSignup(false);
    setShowResetPassword(false);
  };


  const handleForgotPassword = () => {
    setShowLogin(false);
    setShowSignup(false);
    setShowResetPassword(true);
  };



  const handleSwitchToSignup = () => {
    setShowLogin(false);
    setShowResetPassword(false);
    setShowSignup(true);
  };




  const handleSwitchToLogin = () => {
    setShowSignup(false);
    setShowResetPassword(false);
    setShowLogin(true);
  };



  const handleLoginSuccess = (userData: { name: string; email: string }) => {
    setUser(userData);
  };


  const handleLogout = () => {
    localStorage.removeItem("token");
    localStorage.removeItem("user");
    setUser(null);
    setDropdownOpen(false);
  };


  return (
    <nav className="flex justify-between items-center px-xl py-4 bg-white font-sans mx-auto rounded box-border width-full">
      <div className="flex items-center gap-6 space-y-2 m-2">
        <Link href="#" className="link">
          <Image className="max-h-8" src="/Azubi-Logo.svg" alt="logo" width={100} height={100} />
        Link>
        <Link href="/" className="link m-0 block pb-2 text-black text-[16px] font-inter">HomeLink>
        <Link href="/courses" className="link m-0 block pb-2 text-black text-[16px] font-inter">CoursesLink>
      div>


      <div className="relative">
        {user ? (
          // Logged-in UI (Profile Dropdown)
          <div className="relative flex items-center  cursor-pointer" onClick={() => setDropdownOpen(!dropdownOpen)}>
            <div className="w-10 h-10 rounded-full bg-hero-bg flex items-center justify-center mr-[16px] text-white text-lg font-semibold">
            {user?.name
              ? user.name
                  .split(" ") // Split by space into words
                  .map((word) => word.charAt(0).toUpperCase()) // Get first letter of each word
                  .slice(0, 2) // Take only first two words (if available)
                  .join("") // Combine letters
              : "U"}

            div>
            <span className="text-black font-medium">{user.name}span>
            <ChevronDown className="text-black ml-[48px]" />

            {/* Dropdown Menu */}
            {dropdownOpen && (
              <div className="absolute top-[60px] right-0 mt-2 w-48 bg-white border border-gray-300 rounded-md shadow-md z-20">
                <nav className="w-full text-left px-4 py-2 hover:bg-white text-black">
                  <Link href="/" className="block py-2 text-black text-[16px] font-inter hover:text-hero-bg transition-colors duration-200">PortalLink>
                  <Link
                    href="/"
                    onClick={handleLogout}
                    className="block py-2 text-black text-[16px] font-inter hover:text-hero-bg transition-colors duration-200"
                  >
                    Logout
                  Link>
                nav>
              div>


            )}
          div>
        ) : (
          // Login Button
          <button
            className="link bg-transparent text-blue-700 py-3 px-6 border border-blue-700 rounded-md flex items-center gap-3 text-base font-medium transition-colors duration-300 ease-in-out hover:bg-hero-bg hover:text-white font-inter"
            onClick={() => setShowLogin(true)}
          >
            <span className="font-inter">Loginspan>
            <LogIn />
          button>
        )}


        {/* LOGIN / SIGNUP / RESET PASSWORD MODALS */}
        {showLogin || showSignup || showResetPassword ? (
          <div className="absolute top-[70px] right-[0px] z-10">
            {showLogin && (
              <LoginClient
                onClose={handleCloseModals}
                onForgotPassword={handleForgotPassword}
                onSignup={handleSwitchToSignup}
                onLoginSuccess={handleLoginSuccess}
              />
            )}
            {showSignup && (
              <SignupClient
                onClose={handleCloseModals}
                onLogin={handleSwitchToLogin}
              />
            )}
            {showResetPassword && (
  <ResetPassword onClose={handleCloseModals} onSignup={handleSwitchToSignup} />
)}




          div>
        ) : null}
      div>
    nav>
  );
};


export default Navbar;

I know this is a lot to wrap your head around but stay with me or it's not maybe it's part of the day-to-day life of a developer to go through this amount of code. Well, this is my thinking process when building the Navbar components.
This is the relative path to the component src\app\components\LearnerPage\Navbar.tsx. you can get my folder structure for my project in the previous post, ie Update 2

This file defines a navigation bar component for my Next.js application, handling user authentication and navigation.
Here's a detailed explanation:

Navbar Component

1. "use client";

This directive at the beginning of the file indicates that this component is a client-side component. In Next.js, components are server-side rendered by default. "use client" specifies that this component, and any components imported into it, will be rendered in the user's browser. This is necessary because this component uses React hooks like useState and useEffect, which are client-side functionalities.

  • Server-Side Rendering (SSR): The server sends a complete web page to your browser, so it loads quickly and is easier for search engines to understand.
  • Client-Side Rendering (CSR): The server sends basic instructions to your browser, which then builds the web page itself. This can make the first load slower, but moving between pages can be faster after that.

React hooks (e.g., useState, useEffect) are tools that manage things like data changes and side effects, and they work only in the browser environment.

2. Import Statements

import { useState, useEffect, useRef } from "react";
import LoginClient from "../LearnerRegFlow/LoginClient";
import SignupClient from "../LearnerRegFlow/SignupClient";
import ResetPassword from "../LearnerRegFlow/ResetPassword";
import Link from "next/link";
import Image from "next/image";
import { LogIn, ChevronDown } from "lucide-react";


typescript

useState, useEffect, useRef from "react": These are React hooks:

useState: Allows you to add state variables to functional components. State variables are used to manage data that can change and trigger re-renders of the component when they do.

  • State Variables: These are like special containers for data in your component. They can hold any kind of data, such as numbers, text, or objects.
  • Manage Data Changes: When the data inside these state variables changes, React knows something has changed in the component.
  • Trigger Re-renders: When the data changes, React automatically updates the component to show the new data. This process is called re-rendering.

useEffect: Lets you perform side effects in functional components. Side effects are operations that interact with the outside world, like data fetching, DOM manipulation, or timers. In this case, it's used to load user data from local storage.

useRef: While imported, useRef is not actually used in the provided code snippet. It's typically used to directly interact with DOM elements or to hold mutable values that don't trigger re-renders.

  • Interacting with DOM Elements: It allows you to directly interact with elements on the page (like inputs, buttons, etc.) without causing the component to re-render. This is useful for things like focusing an input or measuring an element's size.
  • Holding Mutable Values: You can use useRef to store values that might change but don’t need to trigger a re-render. For example, you might use it to keep track of a timer, or to store a previous value for comparison.

LoginClient from "../LearnerRegFlow/LoginClient": Imports a component named LoginClient. This component is responsible for rendering the login modal or form. It's located in the ../LearnerRegFlow/LoginClient path, a directory structure within the Next.js project.

SignupClient from "../LearnerRegFlow/SignupClient": Imports a component named SignupClient, for the signup modal or form, located in the same directory.

ResetPassword from "../LearnerRegFlow/ResetPassword": Imports the ResetPassword component, for handling the password reset functionality, also from the same directory.

Link from "next/link": Imports the Link component from Next.js. This component is used for client-side navigation between routes in your Next.js application, providing better performance than traditional tags.

Image from "next/image": Imports the Image component from Next.js. This component is used for optimized image rendering, including features like lazy loading and responsive sizing.

{ LogIn, ChevronDown } from "lucide-react": Imports two icons, LogIn and ChevronDown, from the lucide-react icon library. These are likely used for visual elements in the navigation bar.

3. Component Definition:

const Navbar = () => { ... }

This line defines a functional React component named Navbar. Functional components are a common way to create UI elements in React.

4. State Variables:

const [showLogin, setShowLogin] = useState(false);
const [showSignup, setShowSignup] = useState(false);
const [showResetPassword, setShowResetPassword] = useState(false);
const [user, setUser] = useState<{ name: string; email: string } | null>(null);
const [dropdownOpen, setDropdownOpen] = useState(false);
  • showLogin, setShowLogin: A state variable to control the visibility of the login modal. showLogin holds a boolean value (initially false), and setShowLogin is a function to update this value. When showLogin is true, the login modal will be displayed.
  • showSignup, setShowSignup: Similar to showLogin, this controls the visibility of the signup modal.
  • showResetPassword, setShowResetPassword: Controls the visibility of the reset password modal.
  • user, setUser: Stores the logged-in user's information. The type useState<{ name: string; email: string } | null>(null) specifies that user can hold either an object with name and email properties (both strings) or null (if no user is logged in). It's initialized to null.
  • dropdownOpen, setDropdownOpen: Controls whether the user profile dropdown menu is open or closed. It's a boolean state, initially false.

5. useEffect Hook:

useEffect(() => {
    // Load user from localStorage
    const storedUser = localStorage.getItem("user");
    if (storedUser) {
        setUser(JSON.parse(storedUser));
    }
}, []);
  • Runs once after the component mounts (because of the empty dependency array []).
  • localStorage.getItem("user"): Retrieves user data from the browser's localStorage using the key "user".
  • if (storedUser): Checks if user data was found in localStorage.
  • setUser(JSON.parse(storedUser)): Converts stored JSON string into an object and updates user state.

6. Modal Handlers:

const handleCloseModals = () => {
    setShowLogin(false);
    setShowSignup(false);
    setShowResetPassword(false);
};

const handleForgotPassword = () => {
    setShowLogin(false);
    setShowSignup(false);
    setShowResetPassword(true);
};

const handleSwitchToSignup = () => {
    setShowLogin(false);
    setShowResetPassword(false);
    setShowSignup(true);
};

const handleSwitchToLogin = () => {
    setShowSignup(false);
    setShowResetPassword(false);
    setShowLogin(true);
};
  • handleCloseModals: Closes all modals (login, signup, reset password).
  • handleForgotPassword: Closes login and signup modals and opens the reset password modal.
  • handleSwitchToSignup: Closes login and reset password modals and opens the signup modal.
  • handleSwitchToLogin: Closes signup and reset password modals and opens the login modal.

7. Authentication Handlers:

const handleLoginSuccess = (userData: { name: string; email: string }) => {
    setUser(userData);
};

const handleLogout = () => {
    localStorage.removeItem("token");
    localStorage.removeItem("user");
    setUser(null);
    setDropdownOpen(false);
};
  • handleLoginSuccess(userData): Updates user state with received user data (name and email).
  • handleLogout():
    • Removes "token" and "user" from localStorage.
    • Sets user state back to null.
    • Closes the dropdown menu.

8. JSX Structure (the return statement):

return (
    <nav className="flex justify-between items-center px-xl py-4 bg-white font-sans mx-auto rounded box-border width-full">
      <div className="flex items-center gap-6 space-y-2 m-2">
        <Link href="#" className="link">
          <Image className="max-h-8" src="/Azubi-Logo.svg" alt="logo" width={100} height={100} />
        </Link>
        <Link href="/" className="link m-0 block pb-2 text-black text-[16px] font-inter">Home</Link>
        <Link href="/courses" className="link m-0 block pb-2 text-black text-[16px] font-inter">Courses</Link>
      </div>


      <div className="relative">
        {user ? (
          // Logged-in UI (Profile Dropdown)
          <div className="relative flex items-center  cursor-pointer" onClick={() => setDropdownOpen(!dropdownOpen)}>
            <div className="w-10 h-10 rounded-full bg-hero-bg flex items-center justify-center mr-[16px] text-white text-lg font-semibold">
            {user?.name
              ? user.name
                  .split(" ") // Split by space into words
                  .map((word) => word.charAt(0).toUpperCase()) // Get first letter of each word
                  .slice(0, 2) // Take only first two words (if available)
                  .join("") // Combine letters
              : "U"}

            </div>
            <span className="text-black font-medium">{user.name}</span>
            <ChevronDown className="text-black ml-[48px]" />

            {/* Dropdown Menu */}
            {dropdownOpen && (
              <div className="absolute top-[60px] right-0 mt-2 w-48 bg-white border border-gray-300 rounded-md shadow-md z-20">
                <nav className="w-full text-left px-4 py-2 hover:bg-white text-black">
                  <Link href="/" className="block py-2 text-black text-[16px] font-inter hover:text-hero-bg transition-colors duration-200">Portal</Link>
                  <Link
                    href="/"
                    onClick={handleLogout}
                    className="block py-2 text-black text-[16px] font-inter hover:text-hero-bg transition-colors duration-200"
                  >
                    Logout
                  </Link>
                </nav>
              </div>


            )}
          </div>
        ) : (
          // Login Button
          <button
            className="link bg-transparent text-blue-700 py-3 px-6 border border-blue-700 rounded-md flex items-center gap-3 text-base font-medium transition-colors duration-300 ease-in-out hover:bg-hero-bg hover:text-white font-inter"
            onClick={() => setShowLogin(true)}
          >
            <span className="font-inter">Login</span>
            <LogIn />
          </button>
        )}


        {/* LOGIN / SIGNUP / RESET PASSWORD MODALS */}
        {showLogin || showSignup || showResetPassword ? (
          <div className="absolute top-[70px] right-[0px] z-10">
            {showLogin && (
              <LoginClient
                onClose={handleCloseModals}
                onForgotPassword={handleForgotPassword}
                onSignup={handleSwitchToSignup}
                onLoginSuccess={handleLoginSuccess}
              />
            )}
            {showSignup && (
              <SignupClient
                onClose={handleCloseModals}
                onLogin={handleSwitchToLogin}
              />
            )}
            {showResetPassword && (
  <ResetPassword onClose={handleCloseModals} onSignup={handleSwitchToSignup} />
)}




          </div>
        ) : null}
      </div>
    </nav>
  );
};


export default Navbar;

The root element represents the navigation bar. It uses Tailwind CSS classes for styling (e.g., flex, justify-between, items-center, bg-white).

First
(Logo and Nav Links)

 className="flex items-center gap-6 space-y-2 m-2">
     href="#"> ... 
     ... src="/Azubi-Logo.svg" ... />
     href="/">Home
     href="/courses">Courses

Explanation:

  • className="flex items-center gap-6 space-y-2 m-2": Uses flexbox to arrange items horizontally, with spacing and margins.
  • ... : A Next.js Link component for the logo. href="#" might be a placeholder, and you might want to change it to the homepage path (/).
  • : Displays the logo image using the Next.js Image component, referencing an image file in the public directory (/Azubi-Logo.svg).
  • Two more components for "Home" and "Courses" navigation links, pointing to the root path (/) and /courses path, respectively.

Second
(User Authentication and Dropdown)

 className="relative">
    {user ? (
         className="relative flex items-center cursor-pointer" onClick={() => setDropdownOpen(!dropdownOpen)}>
             className="w-10 h-10 rounded-full bg-hero-bg flex items-center justify-center mr-[16px] text-white text-lg font-semibold">
                {user?.name
                    ? user.name
                        .split(" ")
                        .map((word) => word.charAt(0).toUpperCase())
                        .slice(0, 2)
                        .join("")
                    : "U"}
            
className="text-black font-medium">{user.name} className="text-black ml-[48px]" />