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