- block-timeline: ref updates moved to useLayoutEffect - channel-card, guide/page: Date.now() wrapped in useMemo + suppress purity rule - auth-context: lazy localStorage init (removes setState-in-effect) - use-channel-order: lazy localStorage init (removes setState-in-effect) - use-idle: start timer on mount without calling resetIdle (removes setState-in-effect) - use-subtitles, transcode-settings-dialog: inline eslint-disable on exact violating line - providers: block-level eslint-disable for tokenRef closure in useState initializer - edit-channel-sheet: remove unused minsToTime and BlockContent imports - docs/page: escape unescaped quote and apostrophe entities
53 lines
1.2 KiB
TypeScript
53 lines
1.2 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
createContext,
|
|
useContext,
|
|
useState,
|
|
type ReactNode,
|
|
} from "react";
|
|
|
|
const TOKEN_KEY = "k-tv-token";
|
|
|
|
interface AuthContextValue {
|
|
token: string | null;
|
|
/** True once the initial localStorage read has completed */
|
|
isLoaded: boolean;
|
|
setToken: (token: string | null) => void;
|
|
}
|
|
|
|
const AuthContext = createContext<AuthContextValue | null>(null);
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const [token, setTokenState] = useState<string | null>(() => {
|
|
try {
|
|
return localStorage.getItem(TOKEN_KEY);
|
|
} catch {
|
|
return null;
|
|
}
|
|
});
|
|
// isLoaded is always true: lazy init above reads localStorage synchronously
|
|
const [isLoaded] = useState(true);
|
|
|
|
const setToken = (t: string | null) => {
|
|
setTokenState(t);
|
|
if (t) {
|
|
localStorage.setItem(TOKEN_KEY, t);
|
|
} else {
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AuthContext.Provider value={{ token, isLoaded, setToken }}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAuthContext() {
|
|
const ctx = useContext(AuthContext);
|
|
if (!ctx) throw new Error("useAuthContext must be used within AuthProvider");
|
|
return ctx;
|
|
}
|