aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/app/hooks/useAuth.ts
blob: 87627341501c40d1ed7896fd500db6723ea5bb47 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import { useCallback, useSyncExternalStore } from "react";
import { apiLogin } from "../api/client";
import {
	type User,
	clearToken,
	getToken,
	getUserFromToken,
	isTokenExpired,
	setToken,
} from "../auth";

// Simple external store to trigger re-renders when auth state changes.
let authVersion = 0;
const listeners = new Set<() => void>();

function subscribe(callback: () => void) {
	listeners.add(callback);
	return () => listeners.delete(callback);
}

function getSnapshot() {
	return authVersion;
}

function notifyAuthChange() {
	authVersion++;
	for (const listener of listeners) {
		listener();
	}
}

export function useAuth(): {
	user: User | null;
	token: string | null;
	isLoggedIn: boolean;
	login: (username: string, password: string) => Promise<void>;
	logout: () => void;
} {
	useSyncExternalStore(subscribe, getSnapshot);

	const token = getToken();
	const isExpired = isTokenExpired();
	const user = isExpired ? null : getUserFromToken();
	const isLoggedIn = user !== null && !isExpired;

	const login = useCallback(async (username: string, password: string) => {
		const { token } = await apiLogin(username, password);
		setToken(token);
		notifyAuthChange();
	}, []);

	const logout = useCallback(() => {
		clearToken();
		notifyAuthChange();
	}, []);

	return { user, token: isLoggedIn ? token : null, isLoggedIn, login, logout };
}