1import * as React from 'react' 2import * as Kb from '../../common-adapters' 3import * as Constants from '../../constants/settings' 4import * as Container from '../../util/container' 5import * as Platform from '../../constants/platform' 6import * as RouteTreeGen from '../../actions/route-tree-gen' 7import * as Styles from '../../styles' 8import * as SettingsGen from '../../actions/settings-gen' 9import {EnterEmailBody} from '../../signup/email' 10import {EnterPhoneNumberBody} from '../../signup/phone-number' 11import {VerifyBody} from '../../signup/phone-number/verify' 12import {e164ToDisplay} from '../../util/phone-numbers' 13 14export const Email = () => { 15 const dispatch = Container.useDispatch() 16 const nav = Container.useSafeNavigation() 17 18 const [email, onChangeEmail] = React.useState('') 19 const [searchable, onChangeSearchable] = React.useState(true) 20 const [addEmailInProgress, onAddEmailInProgress] = React.useState('') 21 const emailTrimmed = email.trim() 22 const disabled = !emailTrimmed 23 24 const addedEmail = Container.useSelector(state => state.settings.email.addedEmail) 25 const emailError = Container.useSelector(state => state.settings.email.error) 26 const waiting = Container.useAnyWaiting(Constants.addEmailWaitingKey) 27 28 // clean on unmount 29 React.useEffect(() => () => dispatch(SettingsGen.createClearAddingEmail()), [dispatch]) 30 // watch for + nav away on success 31 React.useEffect(() => { 32 if (addedEmail === addEmailInProgress) { 33 // success 34 dispatch(RouteTreeGen.createClearModals()) 35 } 36 }, [addEmailInProgress, addedEmail, dispatch]) 37 // clean on edit 38 React.useEffect(() => { 39 if (emailTrimmed !== addEmailInProgress && emailError) { 40 dispatch(SettingsGen.createClearAddingEmail()) 41 } 42 }, [addEmailInProgress, dispatch, emailError, emailTrimmed]) 43 44 const onClose = React.useCallback(() => dispatch(nav.safeNavigateUpPayload()), [dispatch, nav]) 45 const onContinue = React.useCallback(() => { 46 if (disabled || waiting) { 47 return 48 } 49 onAddEmailInProgress(emailTrimmed) 50 dispatch(SettingsGen.createAddEmail({email: emailTrimmed, searchable: searchable})) 51 }, [disabled, waiting, emailTrimmed, dispatch, searchable]) 52 return ( 53 <Kb.Modal 54 onClose={onClose} 55 header={{ 56 leftButton: Styles.isMobile ? ( 57 <Kb.Text type="BodySemiboldLink" onClick={onClose}> 58 Close 59 </Kb.Text> 60 ) : null, 61 title: Styles.isMobile ? 'Add email address' : 'Add an email address', 62 }} 63 footer={{ 64 content: ( 65 <Kb.ButtonBar style={styles.buttonBar} fullWidth={true}> 66 {!Styles.isMobile && ( 67 <Kb.Button type="Dim" label="Cancel" fullWidth={true} onClick={onClose} disabled={waiting} /> 68 )} 69 <Kb.Button 70 label="Continue" 71 fullWidth={true} 72 onClick={onContinue} 73 disabled={disabled} 74 waiting={waiting} 75 /> 76 </Kb.ButtonBar> 77 ), 78 style: styles.footer, 79 }} 80 mode="Wide" 81 > 82 <Kb.Box2 83 direction="vertical" 84 centerChildren={true} 85 fullWidth={true} 86 fullHeight={true} 87 style={styles.body} 88 > 89 <EnterEmailBody 90 email={email} 91 onChangeEmail={onChangeEmail} 92 showSearchable={true} 93 searchable={searchable} 94 onChangeSearchable={onChangeSearchable} 95 onContinue={onContinue} 96 iconType={ 97 Styles.isMobile 98 ? Platform.isLargeScreen 99 ? 'icon-email-add-96' 100 : 'icon-email-add-64' 101 : 'icon-email-add-64' 102 } 103 /> 104 </Kb.Box2> 105 {!!emailError && ( 106 <Kb.Banner color="red" style={styles.banner}> 107 <Kb.BannerParagraph bannerColor="red" content={emailError} /> 108 </Kb.Banner> 109 )} 110 </Kb.Modal> 111 ) 112} 113export const Phone = () => { 114 const dispatch = Container.useDispatch() 115 const nav = Container.useSafeNavigation() 116 117 const [phoneNumber, onChangeNumber] = React.useState('') 118 const [valid, onChangeValidity] = React.useState(false) 119 const [searchable, onChangeSearchable] = React.useState(true) 120 const disabled = !valid 121 122 const defaultCountry = Container.useSelector(state => state.settings.phoneNumbers.defaultCountry) 123 124 const error = Container.useSelector(state => state.settings.phoneNumbers.error) 125 const pendingVerification = Container.useSelector(state => state.settings.phoneNumbers.pendingVerification) 126 const waiting = Container.useAnyWaiting(Constants.addPhoneNumberWaitingKey) 127 128 // clean only errors on unmount so verify screen still has info 129 React.useEffect(() => () => dispatch(SettingsGen.createClearPhoneNumberErrors()), [dispatch]) 130 // watch for go to verify 131 React.useEffect(() => { 132 if (!error && !!pendingVerification) { 133 dispatch(nav.safeNavigateAppendPayload({path: ['settingsVerifyPhone']})) 134 } 135 }, [dispatch, error, nav, pendingVerification]) 136 // trigger a default phone number country rpc if it's not already loaded 137 React.useEffect(() => { 138 !defaultCountry && dispatch(SettingsGen.createLoadDefaultPhoneNumberCountry()) 139 }, [defaultCountry, dispatch]) 140 141 const onClose = React.useCallback(() => { 142 dispatch(SettingsGen.createClearPhoneNumberAdd()) 143 dispatch(nav.safeNavigateUpPayload()) 144 }, [dispatch, nav]) 145 146 const onContinue = React.useCallback( 147 () => 148 disabled || waiting ? null : dispatch(SettingsGen.createAddPhoneNumber({phoneNumber, searchable})), 149 [dispatch, disabled, waiting, searchable, phoneNumber] 150 ) 151 152 const onChangeNumberCb = React.useCallback((phoneNumber: string, validity: boolean) => { 153 onChangeNumber(phoneNumber) 154 onChangeValidity(validity) 155 }, []) 156 157 return ( 158 <Kb.Modal 159 onClose={onClose} 160 header={{ 161 leftButton: Styles.isMobile ? ( 162 <Kb.Text type="BodySemiboldLink" onClick={onClose}> 163 Close 164 </Kb.Text> 165 ) : null, 166 title: Styles.isMobile ? 'Add phone number' : 'Add a phone number', 167 }} 168 footer={{ 169 content: ( 170 <Kb.ButtonBar style={styles.buttonBar} fullWidth={true}> 171 {!Styles.isMobile && ( 172 <Kb.Button type="Dim" label="Cancel" fullWidth={true} onClick={onClose} disabled={waiting} /> 173 )} 174 <Kb.Button 175 label="Continue" 176 fullWidth={true} 177 onClick={onContinue} 178 disabled={disabled} 179 waiting={waiting} 180 /> 181 </Kb.ButtonBar> 182 ), 183 style: styles.footer, 184 }} 185 mode="Wide" 186 > 187 <Kb.Box2 188 direction="vertical" 189 centerChildren={true} 190 fullWidth={true} 191 fullHeight={true} 192 style={styles.body} 193 > 194 <EnterPhoneNumberBody 195 defaultCountry={defaultCountry} 196 onChangeNumber={onChangeNumberCb} 197 onContinue={onContinue} 198 searchable={searchable} 199 onChangeSearchable={onChangeSearchable} 200 iconType={ 201 Styles.isMobile 202 ? Platform.isLargeScreen 203 ? 'icon-phone-number-add-96' 204 : 'icon-phone-number-add-64' 205 : 'icon-phone-number-add-64' 206 } 207 /> 208 </Kb.Box2> 209 {!!error && ( 210 <Kb.Banner color="red" style={styles.banner}> 211 <Kb.BannerParagraph bannerColor="red" content={error} /> 212 </Kb.Banner> 213 )} 214 </Kb.Modal> 215 ) 216} 217export const VerifyPhone = () => { 218 const dispatch = Container.useDispatch() 219 220 const [code, onChangeCode] = React.useState('') 221 222 const pendingVerification = Container.useSelector(state => state.settings.phoneNumbers.pendingVerification) 223 const error = Container.useSelector(state => state.settings.phoneNumbers.error) 224 const verificationState = Container.useSelector(state => state.settings.phoneNumbers.verificationState) 225 const resendWaiting = Container.useAnyWaiting( 226 Constants.addPhoneNumberWaitingKey, 227 Constants.resendVerificationForPhoneWaitingKey 228 ) 229 const verifyWaiting = Container.useAnyWaiting(Constants.verifyPhoneNumberWaitingKey) 230 231 // clean everything on unmount 232 React.useEffect(() => () => dispatch(SettingsGen.createClearPhoneNumberAdd()), [dispatch]) 233 // Clear on success 234 React.useEffect(() => { 235 if (verificationState === 'success' && !error) { 236 dispatch(RouteTreeGen.createClearModals()) 237 } 238 }, [verificationState, error, dispatch]) 239 240 const onResend = React.useCallback( 241 () => dispatch(SettingsGen.createResendVerificationForPhoneNumber({phoneNumber: pendingVerification})), 242 [dispatch, pendingVerification] 243 ) 244 const onClose = React.useCallback(() => { 245 dispatch(SettingsGen.createClearPhoneNumberAdd()) 246 dispatch(RouteTreeGen.createClearModals()) 247 }, [dispatch]) 248 const onContinue = React.useCallback( 249 () => dispatch(SettingsGen.createVerifyPhoneNumber({code, phoneNumber: pendingVerification})), 250 [dispatch, code, pendingVerification] 251 ) 252 const disabled = !code 253 254 const displayPhone = e164ToDisplay(pendingVerification) 255 return ( 256 <Kb.Modal 257 onClose={onClose} 258 header={{ 259 hideBorder: true, 260 leftButton: Styles.isMobile ? ( 261 <Kb.BackButton onClick={onClose} iconColor={Styles.globalColors.white} /> 262 ) : null, 263 style: styles.blueBackground, 264 title: ( 265 <Kb.Text type="BodySmall" negative={true} center={true}> 266 {displayPhone || 'Unknown number'} 267 </Kb.Text> 268 ), 269 }} 270 footer={{ 271 content: ( 272 <Kb.ButtonBar style={styles.buttonBar} fullWidth={true}> 273 <Kb.Button 274 disabled={disabled} 275 type="Success" 276 label="Continue" 277 onClick={onContinue} 278 waiting={verifyWaiting} 279 fullWidth={true} 280 /> 281 </Kb.ButtonBar> 282 ), 283 hideBorder: true, 284 style: styles.blueBackground, 285 }} 286 mode="Wide" 287 > 288 <Kb.Box2 289 direction="vertical" 290 style={Styles.collapseStyles([ 291 styles.blueBackground, 292 styles.verifyContainer, 293 Styles.globalStyles.flexOne, 294 ])} 295 fullWidth={true} 296 fullHeight={true} 297 centerChildren={true} 298 > 299 <VerifyBody 300 onResend={onResend} 301 resendWaiting={resendWaiting} 302 code={code} 303 onChangeCode={onChangeCode} 304 /> 305 </Kb.Box2> 306 {!!error && ( 307 <Kb.Banner color="red" style={styles.banner}> 308 <Kb.BannerParagraph bannerColor="red" content={error} /> 309 </Kb.Banner> 310 )} 311 </Kb.Modal> 312 ) 313} 314 315const styles = Styles.styleSheetCreate( 316 () => 317 ({ 318 banner: { 319 left: 0, 320 position: 'absolute', 321 right: 0, 322 top: 0, 323 }, 324 blueBackground: { 325 backgroundColor: Styles.globalColors.blue, 326 }, 327 body: { 328 ...Styles.padding( 329 Styles.isMobile ? Styles.globalMargins.tiny : Styles.globalMargins.xlarge, 330 Styles.globalMargins.small, 331 0 332 ), 333 backgroundColor: Styles.globalColors.blueGrey, 334 flexGrow: 1, 335 position: 'relative', 336 }, 337 buttonBar: { 338 minHeight: undefined, 339 }, 340 footer: { 341 ...Styles.padding(Styles.globalMargins.small), 342 }, 343 verifyContainer: { 344 ...Styles.padding(0, Styles.globalMargins.small), 345 }, 346 } as const) 347) 348