this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 231 lines 6.9 kB view raw
1import {useCallback, useEffect, useState} 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} from '@lingui/core/macro' 6import {useLingui} from '@lingui/react' 7import {Trans} from '@lingui/react/macro' 8 9import {logger} from '#/logger' 10import {isSignupQueued, useAgent, useSessionApi} from '#/state/session' 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_LIQUID_GLASS, 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] = useState(false) 31 const [estimatedTime, setEstimatedTime] = useState<string | undefined>( 32 undefined, 33 ) 34 const [placeInQueue, setPlaceInQueue] = useState<number | undefined>( 35 undefined, 36 ) 37 38 const checkStatus = useCallback(async () => { 39 setProcessing(true) 40 try { 41 const res = await 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 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 && !IS_LIQUID_GLASS && ( 111 <SystemBars style={{statusBar: 'light'}} /> 112 )} 113 <ScrollView 114 style={[a.flex_1, t.atoms.bg]} 115 contentContainerStyle={{borderWidth: 0}} 116 bounces={false}> 117 <View 118 style={[ 119 a.flex_row, 120 a.justify_center, 121 gtMobile ? a.pt_4xl : [a.px_xl, a.pt_xl], 122 ]}> 123 <View style={[a.flex_1, {maxWidth: COL_WIDTH}]}> 124 <View 125 style={[a.w_full, a.justify_center, a.align_center, a.my_4xl]}> 126 <Logo width={120} /> 127 </View> 128 129 <Text style={[a.text_4xl, a.font_bold, a.pb_sm]}> 130 <Trans>You're in line</Trans> 131 </Text> 132 <P style={[t.atoms.text_contrast_medium]}> 133 <Trans> 134 There's been a rush of new users to Bluesky! We'll activate your 135 account as soon as we can. 136 </Trans> 137 </P> 138 139 <View 140 style={[ 141 a.rounded_sm, 142 a.px_2xl, 143 a.py_4xl, 144 a.mt_2xl, 145 a.mb_md, 146 a.border, 147 t.atoms.bg_contrast_25, 148 t.atoms.border_contrast_medium, 149 ]}> 150 {typeof placeInQueue === 'number' && ( 151 <Text 152 style={[a.text_5xl, a.text_center, a.font_bold, a.mb_2xl]}> 153 {placeInQueue} 154 </Text> 155 )} 156 <P style={[a.text_center]}> 157 {typeof placeInQueue === 'number' ? ( 158 <Trans>left to go.</Trans> 159 ) : ( 160 <Trans>You are in line.</Trans> 161 )}{' '} 162 {estimatedTime ? ( 163 <Trans> 164 We estimate {estimatedTime} until your account is ready. 165 </Trans> 166 ) : ( 167 <Trans> 168 We will let you know when your account is ready. 169 </Trans> 170 )} 171 </P> 172 </View> 173 174 {webLayout && ( 175 <View 176 style={[ 177 a.w_full, 178 a.flex_row, 179 a.justify_between, 180 a.pt_5xl, 181 {paddingBottom: 200}, 182 ]}> 183 {logoutBtn} 184 {checkBtn} 185 </View> 186 )} 187 </View> 188 </View> 189 </ScrollView> 190 191 {!webLayout && ( 192 <View 193 style={[ 194 a.align_center, 195 t.atoms.bg, 196 gtMobile ? a.px_5xl : a.px_xl, 197 {paddingBottom: Math.max(insets.bottom, a.pb_5xl.paddingBottom)}, 198 ]}> 199 <View style={[a.w_full, a.gap_sm, {maxWidth: COL_WIDTH}]}> 200 {checkBtn} 201 {logoutBtn} 202 </View> 203 </View> 204 )} 205 </Modal> 206 ) 207} 208 209function msToString(ms: number | undefined): string | undefined { 210 if (ms && ms > 0) { 211 const estimatedTimeMins = Math.ceil(ms / 60e3) 212 if (estimatedTimeMins > 59) { 213 const estimatedTimeHrs = Math.round(estimatedTimeMins / 60) 214 if (estimatedTimeHrs > 6) { 215 // dont even bother 216 return undefined 217 } 218 // hours 219 return `${estimatedTimeHrs} ${plural(estimatedTimeHrs, { 220 one: 'hour', 221 other: 'hours', 222 })}` 223 } 224 // minutes 225 return `${estimatedTimeMins} ${plural(estimatedTimeMins, { 226 one: 'minute', 227 other: 'minutes', 228 })}` 229 } 230 return undefined 231}