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