Bluesky app fork with some witchin' additions 💫
at linkat-integration 257 lines 8.9 kB view raw
1import {useEffect, useReducer, useState} from 'react' 2import {AppState, type AppStateStatus, View} from 'react-native' 3import ReactNativeDeviceAttest from 'react-native-device-attest' 4import Animated, {FadeIn, LayoutAnimationConfig} from 'react-native-reanimated' 5import {AppBskyGraphStarterpack} from '@atproto/api' 6import {msg, Trans} from '@lingui/macro' 7import {useLingui} from '@lingui/react' 8 9import {FEEDBACK_FORM_URL} from '#/lib/constants' 10import {logger} from '#/logger' 11import {useServiceQuery} from '#/state/queries/service' 12import {useStarterPackQuery} from '#/state/queries/starter-packs' 13import {useActiveStarterPack} from '#/state/shell/starter-pack' 14import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout' 15import { 16 initialState, 17 reducer, 18 SignupContext, 19 SignupStep, 20 useSubmitSignup, 21} from '#/screens/Signup/state' 22import {StepCaptcha} from '#/screens/Signup/StepCaptcha' 23import {StepHandle} from '#/screens/Signup/StepHandle' 24import {StepInfo} from '#/screens/Signup/StepInfo' 25import {atoms as a, native, useBreakpoints, useTheme} from '#/alf' 26import {AppLanguageDropdown} from '#/components/AppLanguageDropdown' 27import {Divider} from '#/components/Divider' 28import {LinearGradientBackground} from '#/components/LinearGradientBackground' 29import {InlineLinkText} from '#/components/Link' 30import {ScreenTransition} from '#/components/ScreenTransition' 31import {Text} from '#/components/Typography' 32import {useAnalytics} from '#/analytics' 33import {GCP_PROJECT_ID, IS_ANDROID} from '#/env' 34import * as bsky from '#/types/bsky' 35 36export function Signup({ 37 onPressBack, 38 onPressSignIn, 39}: { 40 onPressBack: () => void 41 onPressSignIn: () => void 42}) { 43 const ax = useAnalytics() 44 const {_} = useLingui() 45 const t = useTheme() 46 const [state, dispatch] = useReducer(reducer, { 47 ...initialState, 48 analytics: ax, 49 }) 50 const {gtMobile} = useBreakpoints() 51 const submit = useSubmitSignup() 52 53 useEffect(() => { 54 dispatch({ 55 type: 'setAnalytics', 56 value: ax, 57 }) 58 }, [ax]) 59 60 const activeStarterPack = useActiveStarterPack() 61 const { 62 data: starterPack, 63 isFetching: isFetchingStarterPack, 64 isError: isErrorStarterPack, 65 } = useStarterPackQuery({ 66 uri: activeStarterPack?.uri, 67 }) 68 69 const [isFetchedAtMount] = useState(starterPack != null) 70 const showStarterPackCard = 71 activeStarterPack?.uri && !isFetchingStarterPack && starterPack 72 73 const { 74 data: serviceInfo, 75 isFetching, 76 isError, 77 refetch, 78 } = useServiceQuery(state.serviceUrl) 79 80 useEffect(() => { 81 if (isFetching) { 82 dispatch({type: 'setIsLoading', value: true}) 83 } else if (!isFetching) { 84 dispatch({type: 'setIsLoading', value: false}) 85 } 86 }, [isFetching]) 87 88 useEffect(() => { 89 if (isError) { 90 dispatch({type: 'setServiceDescription', value: undefined}) 91 dispatch({ 92 type: 'setError', 93 value: _( 94 msg`Unable to contact your service. Please check your Internet connection.`, 95 ), 96 }) 97 } else if (serviceInfo) { 98 dispatch({type: 'setServiceDescription', value: serviceInfo}) 99 dispatch({type: 'setError', value: ''}) 100 } 101 }, [_, serviceInfo, isError]) 102 103 useEffect(() => { 104 if (state.pendingSubmit) { 105 if (!state.pendingSubmit.mutableProcessed) { 106 state.pendingSubmit.mutableProcessed = true 107 submit(state, dispatch) 108 } 109 } 110 }, [state, dispatch, submit]) 111 112 // Track app backgrounding during signup 113 useEffect(() => { 114 const subscription = AppState.addEventListener( 115 'change', 116 (nextAppState: AppStateStatus) => { 117 if (nextAppState === 'background') { 118 dispatch({type: 'incrementBackgroundCount'}) 119 } 120 }, 121 ) 122 123 return () => subscription.remove() 124 }, []) 125 126 // On Android, warmup the Play Integrity API on the signup screen so it is ready by the time we get to the gate screen. 127 useEffect(() => { 128 if (!IS_ANDROID) { 129 return 130 } 131 ReactNativeDeviceAttest.warmupIntegrity(GCP_PROJECT_ID).catch(err => 132 logger.error(err), 133 ) 134 }, []) 135 136 return ( 137 <Animated.View exiting={native(FadeIn.duration(90))} style={a.flex_1}> 138 <SignupContext.Provider value={{state, dispatch}}> 139 <LoggedOutLayout 140 leadin="" 141 title={_(msg`Create Account`)} 142 description={_(msg`Welcome to the Atmosphere!`)} 143 scrollable> 144 <View testID="createAccount" style={a.flex_1}> 145 {showStarterPackCard && 146 bsky.dangerousIsType<AppBskyGraphStarterpack.Record>( 147 starterPack.record, 148 AppBskyGraphStarterpack.isRecord, 149 ) ? ( 150 <Animated.View entering={!isFetchedAtMount ? FadeIn : undefined}> 151 <LinearGradientBackground 152 style={[a.mx_lg, a.p_lg, a.gap_sm, a.rounded_sm]}> 153 <Text style={[a.font_semi_bold, a.text_xl, {color: 'white'}]}> 154 {starterPack.record.name} 155 </Text> 156 <Text style={[{color: 'white'}]}> 157 {starterPack.feeds?.length ? ( 158 <Trans> 159 You'll follow the suggested users and feeds once you 160 finish creating your account! 161 </Trans> 162 ) : ( 163 <Trans> 164 You'll follow the suggested users once you finish 165 creating your account! 166 </Trans> 167 )} 168 </Text> 169 </LinearGradientBackground> 170 </Animated.View> 171 ) : null} 172 <LayoutAnimationConfig skipEntering> 173 <ScreenTransition 174 key={state.activeStep} 175 direction={state.screenTransitionDirection}> 176 <View 177 style={[ 178 a.flex_1, 179 a.px_xl, 180 a.pt_2xl, 181 !gtMobile && {paddingBottom: 100}, 182 ]}> 183 <View style={[a.gap_sm, a.pb_3xl]}> 184 <Text 185 style={[a.font_semi_bold, t.atoms.text_contrast_medium]}> 186 <Trans> 187 Step {state.activeStep + 1} of{' '} 188 {state.serviceDescription && 189 !state.serviceDescription.phoneVerificationRequired 190 ? '2' 191 : '3'} 192 </Trans> 193 </Text> 194 <Text style={[a.text_3xl, a.font_semi_bold]}> 195 {state.activeStep === SignupStep.INFO ? ( 196 <Trans>The Atmosphere </Trans> 197 ) : state.activeStep === SignupStep.HANDLE ? ( 198 <Trans>Choose your username</Trans> 199 ) : ( 200 <Trans>Complete the challenge</Trans> 201 )} 202 </Text> 203 </View> 204 205 <LayoutAnimationConfig skipEntering skipExiting> 206 {state.activeStep === SignupStep.INFO ? ( 207 <StepInfo 208 onPressBack={onPressBack} 209 onPressSignIn={onPressSignIn} 210 isLoadingStarterPack={ 211 isFetchingStarterPack && !isErrorStarterPack 212 } 213 isServerError={isError} 214 refetchServer={refetch} 215 /> 216 ) : state.activeStep === SignupStep.HANDLE ? ( 217 <StepHandle /> 218 ) : ( 219 <StepCaptcha /> 220 )} 221 </LayoutAnimationConfig> 222 223 <Divider /> 224 225 <View 226 style={[ 227 a.w_full, 228 a.py_lg, 229 a.flex_row, 230 a.gap_md, 231 a.align_center, 232 ]}> 233 <AppLanguageDropdown /> 234 <Text 235 style={[ 236 a.flex_1, 237 t.atoms.text_contrast_medium, 238 !gtMobile && a.text_md, 239 ]}> 240 <Trans>Having trouble?</Trans>{' '} 241 <InlineLinkText 242 label={_(msg`Contact support`)} 243 to={FEEDBACK_FORM_URL({email: state.email})} 244 style={[!gtMobile && a.text_md]}> 245 <Trans>Open a Tangled Issue</Trans> 246 </InlineLinkText> 247 </Text> 248 </View> 249 </View> 250 </ScreenTransition> 251 </LayoutAnimationConfig> 252 </View> 253 </LoggedOutLayout> 254 </SignupContext.Provider> 255 </Animated.View> 256 ) 257}