From 7f9d53ced1ce4cb60bd3a7fbd4089669a6b608c1 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 4 Aug 2025 14:58:57 +0300 Subject: [PATCH] share logic between typeahead and next button --- src/logger/metrics.ts | 1 - src/screens/Signup/StepHandle.tsx | 23 ++-- src/state/queries/handle-availability.ts | 132 ++++++++++++++++++++++ src/state/queries/handle-availablility.ts | 116 ------------------- 4 files changed, 145 insertions(+), 127 deletions(-) create mode 100644 src/state/queries/handle-availability.ts delete mode 100644 src/state/queries/handle-availablility.ts diff --git a/src/logger/metrics.ts b/src/logger/metrics.ts index 4b1ea5230b..0c9ea1ef63 100644 --- a/src/logger/metrics.ts +++ b/src/logger/metrics.ts @@ -68,7 +68,6 @@ export type MetricEvents = { backgroundCount: number } 'signup:handleTaken': {typeahead?: boolean} - 'signup:handleReserved': {typeahead?: boolean} 'signup:handleAvailable': {typeahead?: boolean} 'signup:handleSuggestionSelected': {method: string} 'signin:hostingProviderPressed': { diff --git a/src/screens/Signup/StepHandle.tsx b/src/screens/Signup/StepHandle.tsx index 60a1b0083e..91f439f106 100644 --- a/src/screens/Signup/StepHandle.tsx +++ b/src/screens/Signup/StepHandle.tsx @@ -18,7 +18,10 @@ import { validateServiceHandle, } from '#/lib/strings/handles' import {logger} from '#/logger' -import {useHandleAvailabilityQuery} from '#/state/queries/handle-availablility' +import { + checkHandleAvailability, + useHandleAvailabilityQuery, +} from '#/state/queries/handle-availability' import {useAgent} from '#/state/session' import {ScreenTransition} from '#/screens/Login/ScreenTransition' import {useSignupContext} from '#/screens/Signup/state' @@ -67,24 +70,24 @@ export function StepHandle() { return } + dispatch({type: 'setIsLoading', value: true}) + try { - dispatch({type: 'setIsLoading', value: true}) + const {available: handleAvailable} = await checkHandleAvailability( + agent, + handle, + state.serviceDescription?.did ?? 'UNKNOWN', + {typeahead: false}, + ) - const res = await agent.resolveHandle({ - handle: createFullHandle(handle, state.userDomain), - }) - - if (res.data.did) { + if (!handleAvailable) { dispatch({ type: 'setError', value: _(msg`That username is already taken`), field: 'handle', }) - logger.metric('signup:handleTaken', {}, {statsig: true}) return } - } catch (e) { - // Don't have to handle } finally { dispatch({type: 'setIsLoading', value: false}) } diff --git a/src/state/queries/handle-availability.ts b/src/state/queries/handle-availability.ts new file mode 100644 index 0000000000..3951ad35e5 --- /dev/null +++ b/src/state/queries/handle-availability.ts @@ -0,0 +1,132 @@ +import {useMemo} from 'react' +import {Agent, ComAtprotoTempCheckHandleAvailability} from '@atproto/api' +import {useQuery} from '@tanstack/react-query' + +import { + BSKY_SERVICE, + BSKY_SERVICE_DID, + PUBLIC_BSKY_SERVICE, +} from '#/lib/constants' +import {createFullHandle} from '#/lib/strings/handles' +import {logger} from '#/logger' +import {useDebouncedValue} from '#/components/live/utils' +import * as bsky from '#/types/bsky' + +export const RQKEY_handleAvailability = ( + handle: string, + domain: string, + serviceDid: string, +) => ['handle-availability', {handle, domain, serviceDid}] + +export function useHandleAvailabilityQuery( + { + username, + serviceDomain, + serviceDid, + enabled, + birthDate, + email, + }: { + username: string + serviceDomain: string + serviceDid: string + enabled: boolean + birthDate?: string + email?: string + }, + debounceDelayMs = 500, +) { + const name = username.trim() + const debouncedHandle = useDebouncedValue(name, debounceDelayMs) + const agent = useMemo(() => { + if (serviceDid === BSKY_SERVICE_DID) { + return new Agent({service: BSKY_SERVICE}) + } else { + return new Agent({service: PUBLIC_BSKY_SERVICE}) + } + }, [serviceDid]) + + return { + enabled: enabled && name === debouncedHandle, + query: useQuery({ + enabled: enabled && name === debouncedHandle, + queryKey: RQKEY_handleAvailability( + debouncedHandle, + serviceDomain, + serviceDid, + ), + queryFn: async () => { + const handle = createFullHandle(name, serviceDomain) + return await checkHandleAvailability(agent, handle, serviceDid, { + email, + birthDate, + typeahead: true, + }) + }, + }), + } +} + +export async function checkHandleAvailability( + agent: Agent, + handle: string, + serviceDid: string, + { + email, + birthDate, + typeahead, + }: { + email?: string + birthDate?: string + typeahead?: boolean + }, +) { + if (serviceDid === BSKY_SERVICE_DID) { + // entryway has a special API for handle availability + const {data} = await agent.com.atproto.temp.checkHandleAvailability({ + handle, + birthDate, + email, + }) + + if ( + bsky.dangerousIsType( + data.result, + ComAtprotoTempCheckHandleAvailability.isResultAvailable, + ) + ) { + logger.metric('signup:handleAvailable', {typeahead}, {statsig: true}) + + return {available: true} as const + } else if ( + bsky.dangerousIsType( + data.result, + ComAtprotoTempCheckHandleAvailability.isResultUnavailable, + ) + ) { + logger.metric('signup:handleTaken', {typeahead}, {statsig: true}) + return { + available: false, + suggestions: data.result.suggestions, + } as const + } else { + throw new Error( + `Unexpected result of \`checkHandleAvailability\`: ${JSON.stringify(data.result)}`, + ) + } + } else { + // 3rd party PDSes won't have this API so just try and resolve the handle + try { + const res = await agent.resolveHandle({ + handle, + }) + + if (res.data.did) { + logger.metric('signup:handleTaken', {typeahead}, {statsig: true}) + return {available: false} as const + } + } catch {} + logger.metric('signup:handleAvailable', {typeahead}, {statsig: true}) + return {available: true} as const + } +} diff --git a/src/state/queries/handle-availablility.ts b/src/state/queries/handle-availablility.ts deleted file mode 100644 index 6f2a83263b..0000000000 --- a/src/state/queries/handle-availablility.ts +++ /dev/null @@ -1,116 +0,0 @@ -import {useMemo} from 'react' -import {Agent, ComAtprotoTempCheckHandleAvailability} from '@atproto/api' -import {useQuery} from '@tanstack/react-query' - -import { - BSKY_SERVICE, - BSKY_SERVICE_DID, - PUBLIC_BSKY_SERVICE, -} from '#/lib/constants' -import {createFullHandle} from '#/lib/strings/handles' -import {logger} from '#/logger' -import {useDebouncedValue} from '#/components/live/utils' -import * as bsky from '#/types/bsky' - -export const RQKEY_handleAvailability = ( - handle: string, - domain: string, - serviceDid: string, -) => ['handle-availability', {handle, domain, serviceDid}] - -export function useHandleAvailabilityQuery( - { - username, - serviceDomain, - serviceDid, - enabled, - birthDate, - email, - }: { - username: string - serviceDomain: string - serviceDid: string - enabled: boolean - birthDate?: string - email?: string - }, - debounceDelayMs = 500, -) { - const name = username.trim() - const debouncedHandle = useDebouncedValue(name, debounceDelayMs) - const agent = useMemo(() => { - if (serviceDid === BSKY_SERVICE_DID) { - return new Agent({service: BSKY_SERVICE}) - } else { - return new Agent({service: PUBLIC_BSKY_SERVICE}) - } - }, [serviceDid]) - - return { - enabled: enabled && name === debouncedHandle, - query: useQuery({ - enabled: enabled && name === debouncedHandle, - queryKey: RQKEY_handleAvailability( - debouncedHandle, - serviceDomain, - serviceDid, - ), - queryFn: async () => { - const handle = createFullHandle(name, serviceDomain) - if (serviceDid === BSKY_SERVICE_DID) { - // entryway has a special API for handle availability - const {data} = await agent.com.atproto.temp.checkHandleAvailability({ - handle, - birthDate, - email, - }) - - if ( - bsky.dangerousIsType( - data.result, - ComAtprotoTempCheckHandleAvailability.isResultAvailable, - ) - ) { - return {available: true} as const - } else if ( - bsky.dangerousIsType( - data.result, - ComAtprotoTempCheckHandleAvailability.isResultUnavailable, - ) - ) { - return { - available: false, - suggestions: data.result.suggestions, - } as const - } else { - throw new Error( - `Unexpected result of \`checkHandleAvailability\`: ${JSON.stringify(data.result)}`, - ) - } - } else { - // 3rd party services won't have this API so just try and resolve the handle - try { - const res = await agent.resolveHandle({ - handle, - }) - - if (res.data.did) { - logger.metric( - 'signup:handleReserved', - {typeahead: true}, - {statsig: true}, - ) - return {available: false} as const - } - } catch {} - logger.metric( - 'signup:handleAvailable', - {typeahead: true}, - {statsig: true}, - ) - return {available: true} as const - } - }, - }), - } -}