profile.tsx148 lines · main
1// @ts-nocheck
2import * as Sentry from '@sentry/nextjs'
3import { useIsLoggedIn, useUser } from 'common'
4import { useRouter } from 'next/router'
5import { createContext, PropsWithChildren, useContext, useEffect, useMemo } from 'react'
6import { toast } from 'sonner'
7
8import { useSignOut } from './auth'
9import { getGitHubProfileImgUrl } from './github'
10import { usePermissionsQuery } from '@/data/permissions/permissions-query'
11import { useProfileCreateMutation } from '@/data/profile/profile-create-mutation'
12import { useProfileIdentitiesQuery } from '@/data/profile/profile-identities-query'
13import { useProfileQuery } from '@/data/profile/profile-query'
14import type { Profile } from '@/data/profile/types'
15import { useSendEventMutation } from '@/data/telemetry/send-event-mutation'
16import type { ResponseError } from '@/types'
17
18export type ProfileContextType = {
19 profile: Profile | undefined
20 error: ResponseError | null
21 isLoading: boolean
22 isError: boolean
23 isSuccess: boolean
24}
25
26export const ProfileContext = createContext<ProfileContextType>({
27 profile: undefined,
28 error: null,
29 isLoading: true,
30 isError: false,
31 isSuccess: false,
32})
33
34export const ProfileProvider = ({ children }: PropsWithChildren<{}>) => {
35 const user = useUser()
36 const isLoggedIn = useIsLoggedIn()
37 const router = useRouter()
38 const signOut = useSignOut()
39
40 const { mutate: sendEvent } = useSendEventMutation()
41 const { mutate: createProfile, isPending: isCreatingProfile } = useProfileCreateMutation({
42 onSuccess: () => {
43 sendEvent({ action: 'sign_up', properties: { category: 'conversion' } })
44
45 if (user) {
46 // Send an event to GTM, will do nothing if GTM is not enabled
47 const thisWindow = window as any
48 thisWindow.dataLayer = thisWindow.dataLayer || []
49 thisWindow.dataLayer.push({
50 event: 'sign_up',
51 email: user.email,
52 })
53 }
54 },
55 onError: (error) => {
56 if (error.code === 409) {
57 // [Joshen] There's currently an assumption that createProfile is getting triggered
58 // multiple times unnecessarily, although the tracing the code i can't see why this might
59 // be happening unless GET profile is somehow returning `User's profile not found` incorrectly
60 // Adding a Sentry capture + toast in hopes to catch this while developing on local / staging
61 Sentry.captureMessage('Profile already exists: ' + error.message)
62 if (process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod') {
63 toast.error('[DEV] createProfile called despite profile already exists: ' + error.message)
64 }
65 } else {
66 Sentry.captureMessage('Failed to create users profile: ' + error.message)
67 toast.error('Failed to create your profile. Please refresh to try again.')
68 }
69 },
70 })
71
72 // Track telemetry for the current user
73 const {
74 error,
75 data: profile,
76 isPending: isLoadingProfile,
77 isError,
78 isSuccess,
79 } = useProfileQuery({
80 enabled: isLoggedIn,
81 })
82
83 useEffect(() => {
84 if (!isError) return
85 // if the user does not yet exist, create a profile for them
86 if (error?.message === "User's profile not found") {
87 createProfile()
88 }
89
90 // [Alaister] If the user has a bad auth token, auth-js won't know about it
91 // and will think the user is authenticated. Since fetching the profile happens
92 // on every page load, we can check for a 401 here and sign the user out if
93 // they have a bad token.
94 if (error?.code === 401) {
95 signOut().then(() => router.push('/sign-in'))
96 }
97 }, [error, signOut, router, createProfile, isError])
98
99 const { isInitialLoading: isLoadingPermissions } = usePermissionsQuery({ enabled: isLoggedIn })
100
101 const value = useMemo(() => {
102 const isLoading = isLoadingProfile || isCreatingProfile || isLoadingPermissions
103
104 return {
105 error,
106 profile,
107 isLoading,
108 isError,
109 isSuccess,
110 }
111 }, [
112 isLoadingProfile,
113 isCreatingProfile,
114 isLoadingPermissions,
115 profile,
116 error,
117 isError,
118 isSuccess,
119 ])
120
121 return <ProfileContext.Provider value={value}>{children}</ProfileContext.Provider>
122}
123
124export const useProfile = () => useContext(ProfileContext)
125
126export function useProfileNameAndPicture(): {
127 username?: string
128 primaryEmail?: string
129 avatarUrl?: string
130 isLoading: boolean
131} {
132 const { profile, isLoading: isLoadingProfile } = useProfile()
133 const { data: identitiesData, isPending: isLoadingIdentities } = useProfileIdentitiesQuery()
134
135 const isGitHubProfile = profile?.auth0_id?.startsWith('github')
136
137 const gitHubUsername = isGitHubProfile
138 ? identitiesData?.identities.find((x) => x.provider === 'github')?.identity_data?.user_name
139 : undefined
140 const avatarUrl = isGitHubProfile ? getGitHubProfileImgUrl(gitHubUsername) : undefined
141
142 return {
143 username: profile?.username,
144 primaryEmail: profile?.primary_email,
145 avatarUrl,
146 isLoading: isLoadingProfile || isLoadingIdentities,
147 }
148}