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}
)
}