import {useState} from 'react' import {View} from 'react-native' import Animated, { FadeIn, FadeOut, LayoutAnimationConfig, LinearTransition, } from 'react-native-reanimated' import {msg, Plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {BSKY_SERVICE_HANDLE_ENDSWITH} from '#/lib/constants' import { createFullHandle, isHandleReserved, MAX_SERVICE_HANDLE_LENGTH, validateServiceHandle, } from '#/lib/strings/handles' import {logger} from '#/logger' import {isNative} from '#/platform/detection' import {useHandleAvailabilityQuery} from '#/state/queries/handle-availablility' import {useAgent} from '#/state/session' import {ScreenTransition} from '#/screens/Login/ScreenTransition' import {useSignupContext} from '#/screens/Signup/state' import {atoms as a, platform, tokens, useTheme, web} from '#/alf' import * as TextField from '#/components/forms/TextField' import {useThrottledValue} from '#/components/hooks/useThrottledValue' import {At_Stroke2_Corner0_Rounded as AtIcon} from '#/components/icons/At' import {Circle_Stroke2_Corner0_Rounded as CircleIcon} from '#/components/icons/Circle' import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheckIcon} from '#/components/icons/CircleCheck' import {CircleX_Stroke2_Corner0_Rounded as CircleXIcon} from '#/components/icons/CircleX' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {BackNextButtons} from './BackNextButtons' export function StepHandle() { const {_} = useLingui() const {state, dispatch} = useSignupContext() const agent = useAgent() const [draftValue, setDraftValue] = useState(state.handle) const isNextLoading = useThrottledValue(state.isLoading, 500) const validCheck = validateServiceHandle(draftValue, state.userDomain) const {data: isHandleAvailable, isLoading} = useHandleAvailabilityQuery( draftValue, state.userDomain, validCheck.overall, ) const onNextPress = async () => { if (!isHandleAvailable?.available) return const handle = draftValue.trim() dispatch({ type: 'setHandle', value: handle, }) if (!validCheck.overall) { return } if ( state.userDomain === BSKY_SERVICE_HANDLE_ENDSWITH && isHandleReserved(handle) ) { dispatch({ type: 'setError', value: _(msg`That username is not available`), field: 'handle', }) logger.metric('signup:handleReserved', {}, {statsig: true}) return } try { dispatch({type: 'setIsLoading', value: true}) const res = await agent.resolveHandle({ handle: createFullHandle(handle, state.userDomain), }) if (res.data.did) { 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}) } logger.metric( 'signup:nextPressed', { activeStep: state.activeStep, phoneVerificationRequired: state.serviceDescription?.phoneVerificationRequired, }, {statsig: true}, ) // phoneVerificationRequired is actually whether a captcha is required if (!state.serviceDescription?.phoneVerificationRequired) { dispatch({ type: 'submit', task: {verificationCode: undefined, mutableProcessed: false}, }) return } dispatch({type: 'next'}) } const onBackPress = () => { const handle = draftValue.trim() dispatch({ type: 'setHandle', value: handle, }) dispatch({type: 'prev'}) logger.metric( 'signup:backPressed', {activeStep: state.activeStep}, {statsig: true}, ) } return ( { if (state.error) { dispatch({type: 'setError', value: ''}) } setDraftValue(val) }} label={state.userDomain} defaultValue={draftValue} keyboardType="ascii-capable" // fix for iOS replacing -- with — autoCapitalize="none" autoCorrect={false} autoFocus autoComplete="off" /> {draftValue.length > 0 && ( {draftValue} )} {state.error && ( {state.error} )} {(isHandleAvailable || isLoading) && validCheck.overall && ( {isLoading ? ( Checking... ) : isHandleAvailable?.available ? ( Available! ) : isHandleAvailable?.reason === 'taken' ? ( Taken ) : ( Invalid )} )} {!validCheck.hyphenStartOrEnd ? ( Doesn't begin or end with a hyphen ) : ( Only contains letters, numbers, and hyphens )} {!validCheck.frontLengthNotTooLong || !validCheck.totalLength ? ( No longer than{' '} ) : ( At least 3 characters )} ) } function Requirement({ children, fade, }: { children: React.ReactNode fade?: boolean }) { return ( {children} ) } function RequirementText({children}: {children: React.ReactNode}) { const t = useTheme() return ( {children} ) } function RequirementIcon({ state, }: { state: 'initial' | 'valid' | 'invalid' | 'loading' }) { const t = useTheme() switch (state) { case 'initial': return case 'valid': return ( ) case 'invalid': return case 'loading': return } } function GhostText({children, value}: {children: string; value: string}) { const t = useTheme() // eslint-disable-next-line bsky-internal/avoid-unwrapped-text return ( {children} {value} ) }