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 refetchOnWindowFocus: false,
65 retry: false,
66 });
67
68 const refreshAuth = useCallback(async () => {
69 await query.refetch();
70 }, [query.refetch]);
71
72 useEffect(() => {
73 // Handle other auth errors
74 if (query.isError && !query.isLoading && pathname !== '/') logout();
75 }, [query.isError, query.isLoading, pathname, logout]);
76
77 // Set super properties for anonymous tracking (no PII)
78 useEffect(() => {
79 const user = query.data;
80
81 // Set super properties when analytics is enabled and user is available
82 // These properties are included in all events automatically
83 if (shouldCaptureAnalytics() && user) {
84 posthog.register({
85 is_internal: isInternalUser(user.handle),
86 is_early_tester: isEarlyTester(user.handle),
87 });
88 }
89 }, [query.data]);
90
91 const contextValue = useMemo<AuthContextType>(
92 () => ({
93 user: query.data ?? null,
94 isAuthenticated: !!query.data,
95 isLoading: query.isLoading,
96 refreshAuth,
97 logout,
98 }),
99 [query.data, query.isLoading, refreshAuth, logout],
100 );
101
102 return <AuthContext value={contextValue}>{children}</AuthContext>;
103};
104
105export const useAuth = (): AuthContextType => {
106 const context = use(AuthContext);
107 if (!context) {
108 throw new Error('useAuth must be used within an AuthProvider');
109 }
110 return context;
111};