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