This repository has no description
1'use client';
2
3import {
4 createContext,
5 useCallback,
6 useContext,
7 useEffect,
8 useMemo,
9 ReactNode,
10} from 'react';
11import { useQuery, useQueryClient } from '@tanstack/react-query';
12import { useRouter } from 'next/navigation';
13import type { GetProfileResponse } from '@/api-client/ApiClient';
14import { ClientCookieAuthService } from '@/services/auth/CookieAuthService.client';
15import { verifySessionOnClient } from '@/lib/auth/dal';
16import { usePathname } from 'next/navigation';
17import posthog from 'posthog-js';
18import { isInternalUser, isEarlyTester } from '@/lib/userLists';
19import { shouldCaptureAnalytics } from '@/features/analytics/utils';
20import { ENABLE_AUTH_LOGGING } from '@/lib/auth/constants';
21
22interface AuthContextType {
23 user: GetProfileResponse | null;
24 isAuthenticated: boolean;
25 isLoading: boolean;
26 refreshAuth: () => Promise<void>;
27 logout: () => Promise<void>;
28}
29
30const AuthContext = createContext<AuthContextType | undefined>(undefined);
31
32export const AuthProvider = ({ children }: { children: ReactNode }) => {
33 const router = useRouter();
34 const queryClient = useQueryClient();
35 const pathname = usePathname(); // to prevent redirecting to login on landing page
36
37 const logout = useCallback(async () => {
38 if (ENABLE_AUTH_LOGGING) {
39 console.log('[useAuth] Initiating logout process');
40 }
41
42 // Reset PostHog user identity
43 if (shouldCaptureAnalytics()) {
44 posthog.reset();
45 if (ENABLE_AUTH_LOGGING) {
46 console.log('[useAuth] PostHog user identity reset');
47 }
48 }
49
50 await ClientCookieAuthService.clearTokens();
51 queryClient.clear();
52 router.push('/login');
53 }, [queryClient, router]);
54
55 const query = useQuery<GetProfileResponse | null>({
56 queryKey: ['authenticated user'],
57 queryFn: async () => {
58 const session = await verifySessionOnClient();
59 return session;
60 },
61 staleTime: 5 * 60 * 1000, // cache for 5 minutes
62 refetchOnWindowFocus: false,
63 retry: false,
64 });
65
66 const refreshAuth = useCallback(async () => {
67 await query.refetch();
68 }, [query.refetch]);
69
70 useEffect(() => {
71 // Handle other auth errors
72 if (query.isError && !query.isLoading && pathname !== '/') logout();
73 }, [query.isError, query.isLoading, pathname, logout]);
74
75 // Set super properties for anonymous tracking (no PII)
76 useEffect(() => {
77 const user = query.data;
78
79 // Set super properties when analytics is enabled and user is available
80 // These properties are included in all events automatically
81 if (shouldCaptureAnalytics() && user) {
82 posthog.register({
83 is_internal: isInternalUser(user.handle),
84 is_early_tester: isEarlyTester(user.handle),
85 });
86 }
87 }, [query.data]);
88
89 const contextValue = useMemo<AuthContextType>(
90 () => ({
91 user: query.data ?? null,
92 isAuthenticated: !!query.data,
93 isLoading: query.isLoading,
94 refreshAuth,
95 logout,
96 }),
97 [query.data, query.isLoading, refreshAuth, logout],
98 );
99
100 return (
101 <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>
102 );
103};
104
105export const useAuth = (): AuthContextType => {
106 const context = useContext(AuthContext);
107 if (!context) {
108 throw new Error('useAuth must be used within an AuthProvider');
109 }
110 return context;
111};