Files
speckle-server/packages/frontend-2/lib/user/composables/emails.ts
T
2025-03-04 13:55:16 +00:00

205 lines
5.8 KiB
TypeScript

import { useApolloClient, useMutation, useQuery } from '@vue/apollo-composable'
import {
settingsNewEmailVerificationMutation,
settingsDeleteUserEmailMutation,
settingsCreateUserEmailMutation
} from '~/lib/settings/graphql/mutations'
import { userEmailsQuery } from '~/lib/user/graphql/queries'
import {
convertThrowIntoFetchResult,
getFirstErrorMessage,
getCacheId,
modifyObjectField
} from '~/lib/common/helpers/graphql'
import type { UserEmail } from '~/lib/common/generated/gql/graphql'
import { useGlobalToast } from '~/lib/common/composables/toast'
import { useMixpanel } from '~/lib/core/composables/mp'
import {
verifyEmailRoute,
homeRoute,
settingsUserRoutes
} from '~/lib/common/helpers/route'
import { verifyEmailMutation } from '~/lib/user/graphql/mutations'
export function useUserEmails() {
const { triggerNotification } = useGlobalToast()
const mixpanel = useMixpanel()
const { result } = useQuery(userEmailsQuery)
const route = useRoute()
const apollo = useApolloClient().client
const { activeUser } = useActiveUser()
const { mutate: resendMutation } = useMutation(settingsNewEmailVerificationMutation)
const { mutate: deleteMutation } = useMutation(settingsDeleteUserEmailMutation)
const { mutate: createMutation } = useMutation(settingsCreateUserEmailMutation)
const { mutate: verifyMutation } = useMutation(verifyEmailMutation)
// Simple array of all emails
const emails = computed(() => result.value?.activeUser?.emails ?? ([] as UserEmail[]))
// Helper computed properties for common queries
const unverifiedPrimaryEmail = computed(
() => emails.value.find((e) => e.primary && !e.verified) || null
)
const unverifiedEmails = computed(() => emails.value.filter((e) => !e.verified))
const addUserEmail = async (email: string) => {
const result = await createMutation({
input: { email }
}).catch(convertThrowIntoFetchResult)
if (result?.data) {
mixpanel.track('Email Added')
navigateTo(verifyEmailRoute)
return true
}
const errorMessage = getFirstErrorMessage(result?.errors)
triggerNotification({
type: ToastNotificationType.Danger,
title: 'Error adding email',
description: errorMessage
})
return false
}
const resendVerificationEmail = async (email: UserEmail) => {
const result = await resendMutation({
input: { id: email.id }
}).catch(convertThrowIntoFetchResult)
if (result?.data) {
triggerNotification({
type: ToastNotificationType.Success,
title: `Verification email sent to ${email.email}`
})
navigateTo(verifyEmailRoute)
return true
}
const errorMessage = getFirstErrorMessage(result?.errors)
triggerNotification({
type: ToastNotificationType.Danger,
title: 'Error sending verification email',
description: errorMessage
})
return false
}
const deleteUserEmail = async (options: {
email: UserEmail
hideToast?: boolean
}) => {
const { email, hideToast } = options
const result = await deleteMutation({
input: { id: email.id }
}).catch(convertThrowIntoFetchResult)
if (result?.data) {
if (!hideToast) {
triggerNotification({
type: ToastNotificationType.Success,
title: 'Deleted email',
description: email.email
})
}
mixpanel.track('Email Deleted')
// If we're on the verify email page and there are no more unverified emails, redirect home
if (route.path === verifyEmailRoute && unverifiedEmails.value.length === 0) {
navigateTo(homeRoute)
}
return true
}
const errorMessage = getFirstErrorMessage(result?.errors)
triggerNotification({
type: ToastNotificationType.Danger,
title: 'Error deleting email',
description: errorMessage
})
return false
}
const verifyUserEmail = async (email: UserEmail, code: string) => {
mixpanel.track('Email Verification Started', {
email: email.email,
isPrimary: email.primary
})
const result = await verifyMutation({
input: { email: email.email, code }
}).catch(convertThrowIntoFetchResult)
const activeUserId = computed(() => activeUser.value?.id)
if (result?.data?.activeUserMutations?.emailMutations?.verify) {
if (!activeUserId.value) return
mixpanel.track('Email Verified', {
email: email.email,
isPrimary: email.primary
})
// Update UserEmail verified status in cache
modifyObjectField(
apollo.cache,
getCacheId('UserEmail', email.id),
'verified',
() => true
)
// Only update User verified status if this is the primary email
if (email.primary) {
modifyObjectField(
apollo.cache,
getCacheId('User', activeUserId.value),
'verified',
() => true
)
navigateTo(homeRoute)
} else {
navigateTo(settingsUserRoutes.emails)
}
modifyObjectField(
apollo.cache,
getCacheId('User', activeUserId.value),
'discoverableWorkspaces',
({ helpers: { evict } }) => evict()
)
triggerNotification({
type: ToastNotificationType.Success,
title: 'Email verified',
description: 'Your email has been successfully verified'
})
return true
}
mixpanel.track('Email Verification Failed', {
email: email.email,
isPrimary: email.primary
})
triggerNotification({
type: ToastNotificationType.Danger,
title: 'Verification failed',
description: 'The verification code you entered is incorrect or expired'
})
return false
}
return {
emails,
unverifiedPrimaryEmail,
unverifiedEmails,
addUserEmail,
resendVerificationEmail,
deleteUserEmail,
verifyUserEmail
}
}