after one click on         Trigger 401/403 Error
it refreshes and send request to protect for 401 like a loop fast i dont want that only one click only one refresh
"use client";

import { useCallback, useEffect, useState } from "react";

const Test = () => {
  const [decodedToken, setDecodedToken] = useState(null);
  const [refreshStatus, setRefreshStatus] = useState("Not triggered");
  const [countdown, setCountdown] = useState(600);
  const [restrictedTokenActions, setTokenAction] = useState(0);

  const apiUrl = "/api/auth";

  const handleSignIn = useCallback(async () => {
    try {
      const response = await fetch(`${apiUrl}/signin`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ username: "user", password: "password" }),
      });

      if (!response.ok)
        throw new Error(`HTTP error! status: ${response.status}`);

      const result = await response.json();
      setDecodedToken(result.decodedToken);
      setRefreshStatus("Token is valid");
    } catch (err) {
      console.error("Sign-in error:", err);
    }
  }, []);

  const handleRefreshToken = useCallback(async () => {
    try {
      const response = await fetch(`${apiUrl}/refresh`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
      });

      if (!response.ok)
        throw new Error(`HTTP error! status: ${response.status}`);

      const result = await response.json();
      setDecodedToken(result.decodedToken);
      setRefreshStatus("Token refreshed successfully");
      return true; // Return true on success
    } catch (err) {
      console.error("Refresh token error:", err);
      setRefreshStatus("Refresh failed. Please log in again.");
      return false; // Return false on failure
    }
  }, []);

  const trigger401Error = async () => {
    try {
      const response = await fetch(`${apiUrl}/protected`, {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
        },
      });

      if (!response.ok) {
        const error: any = new Error(`HTTP error! status: ${response.status}`);
        error.status = response.status;
        throw error;
      }

      const result = await response.json();
      console.log("Protected action success:", result);
    } catch (err: any) {
      console.error("Protected action error:", err);
      if (
        (err.status === 401 || err.status === 403) &&
        restrictedTokenActions < 3
      ) {
        console.log("Handling token refresh and retry...");
        setTokenAction(restrictedTokenActions + 1);
        const refreshSuccessful = await handleRefreshToken();
        if (refreshSuccessful) {
          setTimeout(trigger401Error, 5000); // Pause before retry
        } else {
          console.error("Token refresh failed.");
          setRefreshStatus("Refresh failed. Please log in again.");
        }
      }
    }
  };

  useEffect(() => {
    handleSignIn();
    const refreshInterval = setInterval(handleRefreshToken, 600000);
    const countdownInterval = setInterval(() => {
      setCountdown((prev) => (prev <= 0 ? 600 : prev - 1));
    }, 1000);

    return () => clearInterval(refreshInterval);
    clearInterval(countdownInterval);
  }, [handleSignIn, handleRefreshToken]);

  const formatCountdown = (seconds: any) => {
    const minutes = Math.floor(seconds / 60);
    const remainingSeconds = seconds % 60;
    return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
  };

  return (
    <div className="max-w-2xl mx-auto p-6 bg-gray-100 rounded-lg shadow-md">
      <h2 className="text-2xl font-bold mb-4">Decoded JWT:</h2>
      <pre className="bg-white p-4 rounded-md overflow-auto mb-4">
        {decodedToken
          ? JSON.stringify(decodedToken, null, 2)
          : "No token found"}
      </pre>
      <p className="mb-2">
        <span className="font-semibold">Refresh status:</span> {refreshStatus}
      </p>
      <p className="mb-4">
        <span className="font-semibold">Time until next refresh:</span>{" "}
        {formatCountdown(countdown)}
      </p>
      <button
        onClick={trigger401Error}
        className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded"
      >
        Trigger 401/403 Error
      </button>
    </div>
  );
};

export default Test;
"use server";
import { jwtVerify, SignJWT } from "jose";
import { NextResponse } from "next/server";

function setCookie(res, name, value, options = {}) {
  const {
    httpOnly = true,
    maxAge,
    path = "/",
    secure,
    sameSite = "Lax",
  } = options;
  let cookie = `${name}=${encodeURIComponent(value)}; Path=${path}`;

  if (httpOnly) cookie += "; HttpOnly";
  if (secure) cookie += "; Secure";
  if (sameSite) cookie += `; SameSite=${sameSite}`;
  if (maxAge) cookie += `; Max-Age=${maxAge}`;

  res.setHeader("Set-Cookie", cookie);
}
function getCookie(req, name) {
  const cookieString = req.headers?.cookie || ""; // Safe access property
  const cookies = cookieString.split("; ").reduce((acc, cookie) => {
    const [key, val] = cookie.split("=");
    acc[key] = decodeURIComponent(val);
    return acc;
  }, {});
  return cookies[name];
}
const secretKey = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
const accessTokenExpirationTime = "15m";
const refreshTokenExpirationTime = "7d";
function convertToPlainObject(decodedToken: any) {
  return {
    ...decodedToken.payload,
    protectedHeader: { ...decodedToken.protectedHeader },
  };
}

export async function signIn(req, res) {
  const username = "";

  const accessToken = await new SignJWT({ username })
    .setProtectedHeader({ alg: "HS256" })
    .setExpirationTime(accessTokenExpirationTime)
    .sign(secretKey);

  const refreshToken = await new SignJWT({ username })
    .setProtectedHeader({ alg: "HS256" })
    .setExpirationTime(refreshTokenExpirationTime)
    .sign(secretKey);

  setCookie(res, "accessToken", accessToken, {
    httpOnly: true,
    maxAge: 15 * 60,
  });
  setCookie(res, "refreshToken", refreshToken, {
    httpOnly: true,
    maxAge: 7 * 24 * 60 * 60,
  });

  const decodedToken = await jwtVerify(accessToken, secretKey);
  const plainDecodedToken = convertToPlainObject(decodedToken);

  return NextResponse.json({
    success: true,
    decodedToken: plainDecodedToken,
  });
}

export async function refreshToken(req, res) {
  const refreshToken = getCookie(req, "refreshToken");
  if (!refreshToken) {
    return NextResponse.json(
      { message: "No refresh token found" },
      { status: 401 },
    );
  }
  try {
    const { payload } = await jwtVerify(refreshToken, secretKey);
    const newAccessToken = await new SignJWT(payload)
      .setProtectedHeader({ alg: "HS256" })
      .setExpirationTime(accessTokenExpirationTime)
      .sign(secretKey);

    setCookie(res, "accessToken", newAccessToken, {
      httpOnly: true,
      maxAge: 15 * 60,
    });

    const decodedToken = await jwtVerify(newAccessToken, secretKey);
    const plainDecodedToken = convertToPlainObject(decodedToken);
    return NextResponse.json({
      success: true,
      decodedToken: plainDecodedToken,
    });
  } catch (error) {
    console.error("Error refreshing token:", error);
    return NextResponse.json(
      { message: "Invalid refresh token" },
      { status: 401 },
    );
  }
}

export async function protectedAction(req, res) {
  const accessToken = getCookie(req, "accessToken");
  if (!accessToken) {
    return res.status(401).json({ message: "Unauthorized" });
  }

  try {
    await jwtVerify(accessToken, secretKey);
    return res.json({
      success: true,
      message: "Access granted to protected resource",
    });
  } catch (error) {
    return res.status(401).json({ message: "Invalid access token" });
  }
}