| @@ -1,42 +1,67 @@ | |||||
| // src/app/(main)/axios/AxiosProvider.tsx | |||||
| "use client"; | "use client"; | ||||
| import React, { createContext, useContext, useEffect, useState } from "react"; | |||||
| import React, { createContext, useContext, useEffect, useState, useCallback } from "react"; | |||||
| import axiosInstance, { SetupAxiosInterceptors } from "./axiosInstance"; | import axiosInstance, { SetupAxiosInterceptors } from "./axiosInstance"; | ||||
| const AxiosContext = createContext(axiosInstance); | const AxiosContext = createContext(axiosInstance); | ||||
| const TokenContext = createContext({ | |||||
| setAccessToken: (token: string | null) => {}, | |||||
| const TokenContext = createContext<{ | |||||
| setAccessToken: (token: string | null) => void; | |||||
| }>({ | |||||
| setAccessToken: () => {}, | |||||
| }); | }); | ||||
| export const AxiosProvider: React.FC<{ children: React.ReactNode }> = ({ | |||||
| children, | |||||
| }) => { | |||||
| const [accessToken, setAccessToken] = useState<string | null>( | |||||
| localStorage.getItem("accessToken"), | |||||
| ); | |||||
| export const AxiosProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { | |||||
| const [accessToken, setAccessToken] = useState<string | null>(null); | |||||
| const [isHydrated, setIsHydrated] = useState(false); | |||||
| // Hydrate token only on client | |||||
| useEffect(() => { | |||||
| try { | |||||
| const token = localStorage.getItem("accessToken"); | |||||
| if (token) setAccessToken(token); | |||||
| } catch (e) { | |||||
| console.warn("localStorage unavailable", e); | |||||
| } finally { | |||||
| setIsHydrated(true); | |||||
| } | |||||
| }, []); | |||||
| // Apply token + interceptors | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (accessToken) { | if (accessToken) { | ||||
| axiosInstance.defaults.headers.Authorization = `Bearer ${accessToken}`; | axiosInstance.defaults.headers.Authorization = `Bearer ${accessToken}`; | ||||
| SetupAxiosInterceptors(accessToken); | SetupAxiosInterceptors(accessToken); | ||||
| console.log("[debug] Updated accessToken:", accessToken); | |||||
| } else { | |||||
| delete axiosInstance.defaults.headers.Authorization; | |||||
| } | } | ||||
| }, [accessToken]); | }, [accessToken]); | ||||
| const handleSetAccessToken = useCallback((token: string | null) => { | |||||
| setAccessToken(token); | |||||
| try { | |||||
| if (token) { | |||||
| localStorage.setItem("accessToken", token); | |||||
| } else { | |||||
| localStorage.removeItem("accessToken"); | |||||
| } | |||||
| } catch (e) { | |||||
| // ignore (e.g. private mode) | |||||
| } | |||||
| }, []); | |||||
| // Critical fix: never return null → always return children wrapped in fragment | |||||
| return ( | return ( | ||||
| <AxiosContext.Provider value={axiosInstance}> | <AxiosContext.Provider value={axiosInstance}> | ||||
| <TokenContext.Provider value={{ setAccessToken }}> | |||||
| <TokenContext.Provider value={{ setAccessToken: handleSetAccessToken }}> | |||||
| {/* Render children immediately – they will just not have the token for 1-2ms */} | |||||
| {children} | {children} | ||||
| </TokenContext.Provider> | </TokenContext.Provider> | ||||
| </AxiosContext.Provider> | </AxiosContext.Provider> | ||||
| ); | ); | ||||
| }; | }; | ||||
| // Custom hook to use Axios instance | |||||
| export const useAxios = () => { | |||||
| return useContext(AxiosContext); | |||||
| }; | |||||
| // Custom hook to manage access token | |||||
| export const useToken = () => { | |||||
| return useContext(TokenContext); | |||||
| }; | |||||
| export const useAxios = () => useContext(AxiosContext); | |||||
| export const useToken = () => useContext(TokenContext); | |||||