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