A social knowledge tool for researchers built on ATProto
1'use client';
2
3import { createContext, useContext, ReactNode, useEffect } from 'react';
4import { useQuery, useQueryClient } from '@tanstack/react-query';
5import { useRouter } from 'next/navigation';
6import type { GetProfileResponse } from '@/api-client/ApiClient';
7import { ClientCookieAuthService } from '@/services/auth/CookieAuthService.client';
8import { verifySessionOnClient } from '@/lib/auth/dal';
9import { usePathname } from 'next/navigation';
10
11interface AuthContextType {
12 user: GetProfileResponse | null;
13 isAuthenticated: boolean;
14 isLoading: boolean;
15 refreshAuth: () => Promise<void>;
16 logout: () => Promise<void>;
17}
18
19const AuthContext = createContext<AuthContextType | undefined>(undefined);
20
21export const AuthProvider = ({ children }: { children: ReactNode }) => {
22 const router = useRouter();
23 const queryClient = useQueryClient();
24 const pathname = usePathname(); // to prevent redirecting to login on landing page
25
26 const refreshAuth = async () => {
27 await query.refetch();
28 };
29
30 const logout = async () => {
31 await ClientCookieAuthService.clearTokens();
32 queryClient.clear();
33 router.push('/login');
34 };
35
36 const query = useQuery<GetProfileResponse | null>({
37 queryKey: ['authenticated user'],
38 queryFn: async () => {
39 const session = await verifySessionOnClient();
40 return session;
41 },
42 staleTime: 5 * 60 * 1000, // cache for 5 minutes
43 refetchOnWindowFocus: false,
44 retry: false,
45 });
46
47 useEffect(() => {
48 if (query.isError && !query.isLoading && pathname !== '/') logout();
49 }, [query.isError, logout]);
50
51 const contextValue: AuthContextType = {
52 user: query.data ?? null,
53 isAuthenticated: !!query.data,
54 isLoading: query.isLoading,
55 refreshAuth,
56 logout,
57 };
58
59 return (
60 <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>
61 );
62};
63
64export const useAuth = (): AuthContextType => {
65 const context = useContext(AuthContext);
66 if (!context) {
67 throw new Error('useAuth must be used within an AuthProvider');
68 }
69 return context;
70};