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