"use server";
import { jwtVerify, SignJWT } from "jose";
import { getCookie, setCookie } from "cookies-next";
import { NextResponse } from "next/server";

const secretKey = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
const accessTokenExpirationTime = "15m";
const refreshTokenExpirationTime = "7d";

function convertToPlainObject(decodedToken: any) {
  return {
    ...decodedToken,
    payload: { ...decodedToken.payload },
    protectedHeader: { ...decodedToken.protectedHeader },
  };
}

export async function signIn(username: string, password: string) {
  // Here you would typically validate the username and password
  // For this example, we'll just create a token
  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("accessToken", accessToken, { httpOnly: true });
  setCookie("refreshToken", refreshToken, { httpOnly: true });

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

  return {
    success: true,
    decodedToken: plainDecodedToken,
  };
}

export async function refreshToken() {
  const refreshToken = getCookie("refreshToken");
  if (!refreshToken) {
    throw new Error("No refresh token found");
  }

  try {
    const { payload } = await jwtVerify(refreshToken, secretKey);
    const newAccessToken = await new SignJWT(payload)
      .setProtectedHeader({ alg: "HS256" })
      .setExpirationTime(accessTokenExpirationTime)
      .sign(secretKey);

    setCookie("accessToken", newAccessToken, { httpOnly: true });

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

    return {
      success: true,
      decodedToken: plainDecodedToken,
    };
  } catch (error) {
    console.error("Error refreshing token:", error); // Log error for debugging
    throw new Error("Invalid refresh token");
  }
}

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

  try {
    await jwtVerify(accessToken, secretKey);
    return { success: true, message: "Access granted to protected resource" };
  } catch (error) {
    throw new Error("Invalid access token");
  }
}
"use client";

import { useCallback, useEffect, useState } from "react";
import { protectedAction, refreshToken, signIn } from "@/src/lib/actions/auth";

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

  const handleSignIn = useCallback(async () => {
    try {
      const result: any = await signIn("user", "password");
      const { signature, header, ...plainDecodedToken } =
        result?.decodedToken?.payload;
      setDecodedToken(plainDecodedToken);
      setRefreshStatus("Token is valid");
    } catch (err) {
      console.error("Sign-in error:", err);
    }
  }, []);

  const trigger401Error = async () => {
    try {
      await protectedAction();
    } catch (err: any) {
      console.error("Protected action error:", err);
      if (err.status === 401 || err.status === 403) {
        try {
          await handleRefreshToken();
          // Retry the original request after successful token refresh
          await protectedAction();
        } catch (refreshError) {
          console.error("Token refresh failed:", refreshError);
          setRefreshStatus("Refresh failed. Please log in again.");
          // Optionally, redirect to login page or show login modal
        }
      } else {
        console.error("Unexpected error:", err);
      }
    }
  };

  const handleRefreshToken = useCallback(async () => {
    try {
      const result: any = await refreshToken();
      const { signature, header, ...plainDecodedToken } =
        result?.decodedToken?.payload;
      setDecodedToken(plainDecodedToken);
      setRefreshStatus("Token refreshed successfully");
      return result;
    } catch (err) {
      console.error("Refresh token error:", err);
      setRefreshStatus("Refresh failed. Please log in again.");
      throw err;
    }
  }, []);

  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: number) => {
    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;
digest: "2683469170"
 POST /test 200 in 23ms
Middleware executing for path: /test
Path is public, continuing...
 ⨯ Internal error: Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.
    at eF (E:\BT-Projects\phoenix-next-app\node_modules\.pnpm\next@14.2.14_react-dom@18.3.1_react@18.3.1__react@18.3.1\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:35:270333)
    at eq (E:\BT-Projects\phoenix-next-app\node_modules\.pnpm\next@14.2.14_react-dom@18.3.1_react@18.3.1__react@18.3.1\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:35:274547)
    at eJ (E:\BT-Projects\phoenix-next-app\node_modules\.pnpm\next@14.2.14_react-dom@18.3.1_react@18.3.1__react@18.3.1\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:35:275164)
    at Timeout._onTimeout (E:\BT-Projects\phoenix-next-app\node_modules\.pnpm\next@14.2.14_react-dom@18.3.1_react@18.3.1__react@18.3.1\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:35:264951)
    at listOnTimeout (node:internal/timers:581:17)
    at process.processTimers (node:internal/timers:519:7)
digest: "2683469170"
 POST /test 200 in 31ms
fix it i neeed it to be fixed what is with nextjs?