forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import React from 'react'
2import {Modal, ScrollView, View} from 'react-native'
3import {SystemBars} from 'react-native-edge-to-edge'
4import {useSafeAreaInsets} from 'react-native-safe-area-context'
5import {msg, plural, Trans} from '@lingui/macro'
6import {useLingui} from '@lingui/react'
7
8import {logger} from '#/logger'
9import {isSignupQueued, useAgent, useSessionApi} from '#/state/session'
10import {pdsAgent} from '#/state/session/agent'
11import {useOnboardingDispatch} from '#/state/shell'
12import {Logo} from '#/view/icons/Logo'
13import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf'
14import {Button, ButtonIcon, ButtonText} from '#/components/Button'
15import {Loader} from '#/components/Loader'
16import {P, Text} from '#/components/Typography'
17import {IS_IOS, IS_WEB} from '#/env'
18
19const COL_WIDTH = 400
20
21export function SignupQueued() {
22 const {_} = useLingui()
23 const t = useTheme()
24 const insets = useSafeAreaInsets()
25 const {gtMobile} = useBreakpoints()
26 const onboardingDispatch = useOnboardingDispatch()
27 const {logoutCurrentAccount} = useSessionApi()
28 const agent = useAgent()
29
30 const [isProcessing, setProcessing] = React.useState(false)
31 const [estimatedTime, setEstimatedTime] = React.useState<string | undefined>(
32 undefined,
33 )
34 const [placeInQueue, setPlaceInQueue] = React.useState<number | undefined>(
35 undefined,
36 )
37
38 const checkStatus = React.useCallback(async () => {
39 setProcessing(true)
40 try {
41 const res = await pdsAgent(agent).com.atproto.temp.checkSignupQueue()
42 if (res.data.activated) {
43 // ready to go, exchange the access token for a usable one and kick off onboarding
44 await agent.sessionManager.refreshSession()
45 if (!isSignupQueued(agent.session?.accessJwt)) {
46 onboardingDispatch({type: 'start'})
47 }
48 } else {
49 // not ready, update UI
50 setEstimatedTime(msToString(res.data.estimatedTimeMs))
51 if (typeof res.data.placeInQueue !== 'undefined') {
52 setPlaceInQueue(Math.max(res.data.placeInQueue, 1))
53 }
54 }
55 } catch (e: any) {
56 logger.error('Failed to check signup queue', {err: e.toString()})
57 } finally {
58 setProcessing(false)
59 }
60 }, [
61 setProcessing,
62 setEstimatedTime,
63 setPlaceInQueue,
64 onboardingDispatch,
65 agent,
66 ])
67
68 React.useEffect(() => {
69 checkStatus()
70 const interval = setInterval(checkStatus, 60e3)
71 return () => clearInterval(interval)
72 }, [checkStatus])
73
74 const checkBtn = (
75 <Button
76 variant="solid"
77 color="primary"
78 size="large"
79 label={_(msg`Check my status`)}
80 onPress={checkStatus}
81 disabled={isProcessing}>
82 <ButtonText>
83 <Trans>Check my status</Trans>
84 </ButtonText>
85 {isProcessing && <ButtonIcon icon={Loader} />}
86 </Button>
87 )
88
89 const logoutBtn = (
90 <Button
91 variant="ghost"
92 size="large"
93 color="primary"
94 label={_(msg`Sign out`)}
95 onPress={() => logoutCurrentAccount('SignupQueued')}>
96 <ButtonText>
97 <Trans>Sign out</Trans>
98 </ButtonText>
99 </Button>
100 )
101
102 const webLayout = IS_WEB && gtMobile
103
104 return (
105 <Modal
106 visible
107 animationType={native('slide')}
108 presentationStyle="formSheet"
109 style={[web(a.util_screen_outer)]}>
110 {IS_IOS && <SystemBars style={{statusBar: 'light'}} />}
111 <ScrollView
112 style={[a.flex_1, t.atoms.bg]}
113 contentContainerStyle={{borderWidth: 0}}
114 bounces={false}>
115 <View
116 style={[
117 a.flex_row,
118 a.justify_center,
119 gtMobile ? a.pt_4xl : [a.px_xl, a.pt_xl],
120 ]}>
121 <View style={[a.flex_1, {maxWidth: COL_WIDTH}]}>
122 <View
123 style={[a.w_full, a.justify_center, a.align_center, a.my_4xl]}>
124 <Logo width={120} />
125 </View>
126
127 <Text style={[a.text_4xl, a.font_bold, a.pb_sm]}>
128 <Trans>You're in line</Trans>
129 </Text>
130 <P style={[t.atoms.text_contrast_medium]}>
131 <Trans>
132 There's been a rush of new users to Bluesky! We'll activate your
133 account as soon as we can.
134 </Trans>
135 </P>
136
137 <View
138 style={[
139 a.rounded_sm,
140 a.px_2xl,
141 a.py_4xl,
142 a.mt_2xl,
143 a.mb_md,
144 a.border,
145 t.atoms.bg_contrast_25,
146 t.atoms.border_contrast_medium,
147 ]}>
148 {typeof placeInQueue === 'number' && (
149 <Text
150 style={[a.text_5xl, a.text_center, a.font_bold, a.mb_2xl]}>
151 {placeInQueue}
152 </Text>
153 )}
154 <P style={[a.text_center]}>
155 {typeof placeInQueue === 'number' ? (
156 <Trans>left to go.</Trans>
157 ) : (
158 <Trans>You are in line.</Trans>
159 )}{' '}
160 {estimatedTime ? (
161 <Trans>
162 We estimate {estimatedTime} until your account is ready.
163 </Trans>
164 ) : (
165 <Trans>
166 We will let you know when your account is ready.
167 </Trans>
168 )}
169 </P>
170 </View>
171
172 {webLayout && (
173 <View
174 style={[
175 a.w_full,
176 a.flex_row,
177 a.justify_between,
178 a.pt_5xl,
179 {paddingBottom: 200},
180 ]}>
181 {logoutBtn}
182 {checkBtn}
183 </View>
184 )}
185 </View>
186 </View>
187 </ScrollView>
188
189 {!webLayout && (
190 <View
191 style={[
192 a.align_center,
193 t.atoms.bg,
194 gtMobile ? a.px_5xl : a.px_xl,
195 {paddingBottom: Math.max(insets.bottom, a.pb_5xl.paddingBottom)},
196 ]}>
197 <View style={[a.w_full, a.gap_sm, {maxWidth: COL_WIDTH}]}>
198 {checkBtn}
199 {logoutBtn}
200 </View>
201 </View>
202 )}
203 </Modal>
204 )
205}
206
207function msToString(ms: number | undefined): string | undefined {
208 if (ms && ms > 0) {
209 const estimatedTimeMins = Math.ceil(ms / 60e3)
210 if (estimatedTimeMins > 59) {
211 const estimatedTimeHrs = Math.round(estimatedTimeMins / 60)
212 if (estimatedTimeHrs > 6) {
213 // dont even bother
214 return undefined
215 }
216 // hours
217 return `${estimatedTimeHrs} ${plural(estimatedTimeHrs, {
218 one: 'hour',
219 other: 'hours',
220 })}`
221 }
222 // minutes
223 return `${estimatedTimeMins} ${plural(estimatedTimeMins, {
224 one: 'minute',
225 other: 'minutes',
226 })}`
227 }
228 return undefined
229}