diff options
Diffstat (limited to 'src/components/dialogs/EmailDialog/data')
6 files changed, 214 insertions, 0 deletions
diff --git a/src/components/dialogs/EmailDialog/data/useAccountEmailState.ts b/src/components/dialogs/EmailDialog/data/useAccountEmailState.ts new file mode 100644 index 000000000..377411107 --- /dev/null +++ b/src/components/dialogs/EmailDialog/data/useAccountEmailState.ts @@ -0,0 +1,79 @@ +import {useCallback, useEffect, useState} from 'react' +import {useQuery, useQueryClient} from '@tanstack/react-query' + +import {useAgent} from '#/state/session' +import {emitEmailVerified} from '#/components/dialogs/EmailDialog/events' + +export type AccountEmailState = { + isEmailVerified: boolean + email2FAEnabled: boolean +} + +export const accountEmailStateQueryKey = ['accountEmailState'] as const + +export function useInvalidateAccountEmailState() { + const qc = useQueryClient() + + return useCallback(() => { + return qc.invalidateQueries({ + queryKey: accountEmailStateQueryKey, + }) + }, [qc]) +} + +export function useUpdateAccountEmailStateQueryCache() { + const qc = useQueryClient() + + return useCallback( + (data: AccountEmailState) => { + return qc.setQueriesData( + { + queryKey: accountEmailStateQueryKey, + }, + data, + ) + }, + [qc], + ) +} + +export function useAccountEmailState() { + const agent = useAgent() + const [prevIsEmailVerified, setPrevEmailIsVerified] = useState( + !!agent.session?.emailConfirmed, + ) + const fallbackData: AccountEmailState = { + isEmailVerified: !!agent.session?.emailConfirmed, + email2FAEnabled: !!agent.session?.emailAuthFactor, + } + const query = useQuery<AccountEmailState>({ + enabled: !!agent.session, + refetchOnWindowFocus: true, + queryKey: accountEmailStateQueryKey, + queryFn: async () => { + // will also trigger updates to `#/state/session` data + const {data} = await agent.resumeSession(agent.session!) + return { + isEmailVerified: !!data.emailConfirmed, + email2FAEnabled: !!data.emailAuthFactor, + } + }, + }) + + const state = query.data ?? fallbackData + + /* + * This will emit `n` times for each instance of this hook. So the listeners + * all use `once` to prevent multiple handlers firing. + */ + useEffect(() => { + if (state.isEmailVerified && !prevIsEmailVerified) { + setPrevEmailIsVerified(true) + emitEmailVerified() + } else if (!state.isEmailVerified && prevIsEmailVerified) { + setPrevEmailIsVerified(false) + } + }, [state, prevIsEmailVerified]) + + return state +} diff --git a/src/components/dialogs/EmailDialog/data/useConfirmEmail.ts b/src/components/dialogs/EmailDialog/data/useConfirmEmail.ts new file mode 100644 index 000000000..73f824fcc --- /dev/null +++ b/src/components/dialogs/EmailDialog/data/useConfirmEmail.ts @@ -0,0 +1,29 @@ +import {useMutation} from '@tanstack/react-query' + +import {useAgent, useSession} from '#/state/session' +import {useUpdateAccountEmailStateQueryCache} from '#/components/dialogs/EmailDialog/data/useAccountEmailState' + +export function useConfirmEmail() { + const agent = useAgent() + const {currentAccount} = useSession() + const updateAccountEmailStateQueryCache = + useUpdateAccountEmailStateQueryCache() + + return useMutation({ + mutationFn: async ({token}: {token: string}) => { + if (!currentAccount?.email) { + throw new Error('No email found for the current account') + } + + await agent.com.atproto.server.confirmEmail({ + email: currentAccount.email, + token: token.trim(), + }) + const {data} = await agent.resumeSession(agent.session!) + updateAccountEmailStateQueryCache({ + isEmailVerified: !!data.emailConfirmed, + email2FAEnabled: !!data.emailAuthFactor, + }) + }, + }) +} diff --git a/src/components/dialogs/EmailDialog/data/useManageEmail2FA.ts b/src/components/dialogs/EmailDialog/data/useManageEmail2FA.ts new file mode 100644 index 000000000..39f5fd2d9 --- /dev/null +++ b/src/components/dialogs/EmailDialog/data/useManageEmail2FA.ts @@ -0,0 +1,35 @@ +import {useMutation} from '@tanstack/react-query' + +import {useAgent, useSession} from '#/state/session' +import {useUpdateAccountEmailStateQueryCache} from '#/components/dialogs/EmailDialog/data/useAccountEmailState' + +export function useManageEmail2FA() { + const agent = useAgent() + const {currentAccount} = useSession() + const updateAccountEmailStateQueryCache = + useUpdateAccountEmailStateQueryCache() + + return useMutation({ + mutationFn: async ({ + enabled, + token, + }: + | {enabled: true; token?: undefined} + | {enabled: false; token: string}) => { + if (!currentAccount?.email) { + throw new Error('No email found for the current account') + } + + await agent.com.atproto.server.updateEmail({ + email: currentAccount.email, + emailAuthFactor: enabled, + token, + }) + const {data} = await agent.resumeSession(agent.session!) + updateAccountEmailStateQueryCache({ + isEmailVerified: !!data.emailConfirmed, + email2FAEnabled: !!data.emailAuthFactor, + }) + }, + }) +} diff --git a/src/components/dialogs/EmailDialog/data/useRequestEmailUpdate.ts b/src/components/dialogs/EmailDialog/data/useRequestEmailUpdate.ts new file mode 100644 index 000000000..a442662fc --- /dev/null +++ b/src/components/dialogs/EmailDialog/data/useRequestEmailUpdate.ts @@ -0,0 +1,13 @@ +import {useMutation} from '@tanstack/react-query' + +import {useAgent} from '#/state/session' + +export function useRequestEmailUpdate() { + const agent = useAgent() + + return useMutation({ + mutationFn: async () => { + return (await agent.com.atproto.server.requestEmailUpdate()).data + }, + }) +} diff --git a/src/components/dialogs/EmailDialog/data/useRequestEmailVerification.ts b/src/components/dialogs/EmailDialog/data/useRequestEmailVerification.ts new file mode 100644 index 000000000..ae308c7af --- /dev/null +++ b/src/components/dialogs/EmailDialog/data/useRequestEmailVerification.ts @@ -0,0 +1,13 @@ +import {useMutation} from '@tanstack/react-query' + +import {useAgent} from '#/state/session' + +export function useRequestEmailVerification() { + const agent = useAgent() + + return useMutation({ + mutationFn: async () => { + await agent.com.atproto.server.requestEmailConfirmation() + }, + }) +} diff --git a/src/components/dialogs/EmailDialog/data/useUpdateEmail.ts b/src/components/dialogs/EmailDialog/data/useUpdateEmail.ts new file mode 100644 index 000000000..2ec1eb6dc --- /dev/null +++ b/src/components/dialogs/EmailDialog/data/useUpdateEmail.ts @@ -0,0 +1,45 @@ +import {useMutation} from '@tanstack/react-query' + +import {useAgent} from '#/state/session' +import {useRequestEmailUpdate} from '#/components/dialogs/EmailDialog/data/useRequestEmailUpdate' + +async function updateEmailAndRefreshSession( + agent: ReturnType<typeof useAgent>, + email: string, + token?: string, +) { + await agent.com.atproto.server.updateEmail({email: email.trim(), token}) + await agent.resumeSession(agent.session!) +} + +export function useUpdateEmail() { + const agent = useAgent() + const {mutateAsync: requestEmailUpdate} = useRequestEmailUpdate() + + return useMutation< + {status: 'tokenRequired' | 'success'}, + Error, + {email: string; token?: string} + >({ + mutationFn: async ({email, token}: {email: string; token?: string}) => { + if (token) { + await updateEmailAndRefreshSession(agent, email, token) + return { + status: 'success', + } + } else { + const {tokenRequired} = await requestEmailUpdate() + if (tokenRequired) { + return { + status: 'tokenRequired', + } + } else { + await updateEmailAndRefreshSession(agent, email, token) + return { + status: 'success', + } + } + } + }, + }) +} |