diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/dialogs/VerifyEmailDialog.tsx | 254 | ||||
-rw-r--r-- | src/view/com/composer/videos/SelectVideoBtn.tsx | 37 | ||||
-rw-r--r-- | src/view/screens/Settings/index.tsx | 6 |
3 files changed, 276 insertions, 21 deletions
diff --git a/src/components/dialogs/VerifyEmailDialog.tsx b/src/components/dialogs/VerifyEmailDialog.tsx new file mode 100644 index 000000000..8dfb9bc49 --- /dev/null +++ b/src/components/dialogs/VerifyEmailDialog.tsx @@ -0,0 +1,254 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {cleanError} from '#/lib/strings/errors' +import {logger} from '#/logger' +import {useModalControls} from '#/state/modals' +import {useAgent, useSession} from '#/state/session' +import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' +import {atoms as a, useBreakpoints} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import * as TextField from '#/components/forms/TextField' +import {InlineLinkText} from '#/components/Link' +import {Loader} from '#/components/Loader' +import {Text} from '#/components/Typography' + +export function VerifyEmailDialog({ + control, +}: { + control: Dialog.DialogControlProps +}) { + const agent = useAgent() + + const [didVerify, setDidVerify] = React.useState(false) + + return ( + <Dialog.Outer + control={control} + onClose={async () => { + if (!didVerify) { + return + } + + try { + await agent.resumeSession(agent.session!) + } catch (e: unknown) { + logger.error(String(e)) + return + } + }}> + <Dialog.Handle /> + <Inner control={control} setDidVerify={setDidVerify} /> + </Dialog.Outer> + ) +} + +export function Inner({ + control, + setDidVerify, +}: { + control: Dialog.DialogControlProps + setDidVerify: (value: boolean) => void +}) { + const {_} = useLingui() + const {currentAccount} = useSession() + const agent = useAgent() + const {openModal} = useModalControls() + const {gtMobile} = useBreakpoints() + + const [currentStep, setCurrentStep] = React.useState< + 'StepOne' | 'StepTwo' | 'StepThree' + >('StepOne') + const [confirmationCode, setConfirmationCode] = React.useState('') + const [isProcessing, setIsProcessing] = React.useState(false) + const [error, setError] = React.useState('') + + const uiStrings = { + StepOne: { + title: _(msg`Verify Your Email`), + message: '', + }, + StepTwo: { + title: _(msg`Enter Code`), + message: _( + msg`An email has been sent! Please enter the confirmation code included in the email below.`, + ), + }, + StepThree: { + title: _(msg`Success!`), + message: _(msg`Thank you! Your email has been successfully verified.`), + }, + } + + const onSendEmail = async () => { + setError('') + setIsProcessing(true) + try { + await agent.com.atproto.server.requestEmailConfirmation() + setCurrentStep('StepTwo') + } catch (e: unknown) { + setError(cleanError(e)) + } finally { + setIsProcessing(false) + } + } + + const onVerifyEmail = async () => { + setError('') + setIsProcessing(true) + try { + await agent.com.atproto.server.confirmEmail({ + email: (currentAccount?.email || '').trim(), + token: confirmationCode.trim(), + }) + } catch (e: unknown) { + setError(cleanError(String(e))) + setIsProcessing(false) + return + } + + setIsProcessing(false) + setDidVerify(true) + setCurrentStep('StepThree') + } + + return ( + <Dialog.ScrollableInner + label={_(msg`Verify email dialog`)} + style={[ + gtMobile ? {width: 'auto', maxWidth: 400, minWidth: 200} : a.w_full, + ]}> + <Dialog.Close /> + <View style={[a.gap_xl]}> + <View style={[a.gap_sm]}> + <Text style={[a.font_heavy, a.text_2xl]}> + {uiStrings[currentStep].title} + </Text> + {error ? ( + <View style={[a.rounded_sm, a.overflow_hidden]}> + <ErrorMessage message={error} /> + </View> + ) : null} + <Text style={[a.text_md, a.leading_snug]}> + {currentStep === 'StepOne' ? ( + <> + <Trans> + You'll receive an email at{' '} + <Text style={[a.text_md, a.leading_snug, a.font_bold]}> + {currentAccount?.email} + </Text>{' '} + to verify it's you. + </Trans>{' '} + <InlineLinkText + to="#" + label={_(msg`Change email address`)} + style={[a.text_md, a.leading_snug]} + onPress={e => { + e.preventDefault() + control.close(() => { + openModal({name: 'change-email'}) + }) + return false + }}> + <Trans>Need to change it?</Trans> + </InlineLinkText> + </> + ) : ( + uiStrings[currentStep].message + )} + </Text> + </View> + {currentStep === 'StepTwo' ? ( + <View> + <TextField.LabelText> + <Trans>Confirmation Code</Trans> + </TextField.LabelText> + <TextField.Root> + <TextField.Input + label={_(msg`Confirmation code`)} + placeholder="XXXXX-XXXXX" + onChangeText={setConfirmationCode} + /> + </TextField.Root> + </View> + ) : null} + <View style={[a.gap_sm, gtMobile && [a.flex_row_reverse, a.ml_auto]]}> + {currentStep === 'StepOne' ? ( + <> + <Button + label={_(msg`Send confirmation email`)} + variant="solid" + color="primary" + size="large" + disabled={isProcessing} + onPress={onSendEmail}> + <ButtonText> + <Trans>Send Confirmation</Trans> + </ButtonText> + {isProcessing ? ( + <Loader size="sm" style={[{color: 'white'}]} /> + ) : null} + </Button> + <Button + label={_(msg`I Have a Code`)} + variant="solid" + color="secondary" + size="large" + disabled={isProcessing} + onPress={() => setCurrentStep('StepTwo')}> + <ButtonText> + <Trans>I Have a Code</Trans> + </ButtonText> + </Button> + </> + ) : currentStep === 'StepTwo' ? ( + <> + <Button + label={_(msg`Confirm`)} + variant="solid" + color="primary" + size="large" + disabled={isProcessing} + onPress={onVerifyEmail}> + <ButtonText> + <Trans>Confirm</Trans> + </ButtonText> + {isProcessing ? ( + <Loader size="sm" style={[{color: 'white'}]} /> + ) : null} + </Button> + <Button + label={_(msg`Resend Email`)} + variant="solid" + color="secondary" + size="large" + disabled={isProcessing} + onPress={() => { + setConfirmationCode('') + setCurrentStep('StepOne') + }}> + <ButtonText> + <Trans>Resend Email</Trans> + </ButtonText> + </Button> + </> + ) : currentStep === 'StepThree' ? ( + <Button + label={_(msg`Confirm`)} + variant="solid" + color="primary" + size="large" + onPress={() => control.close()}> + <ButtonText> + <Trans>Close</Trans> + </ButtonText> + </Button> + ) : null} + </View> + </View> + </Dialog.ScrollableInner> + ) +} diff --git a/src/view/com/composer/videos/SelectVideoBtn.tsx b/src/view/com/composer/videos/SelectVideoBtn.tsx index bbb3d95f2..2ba003a6d 100644 --- a/src/view/com/composer/videos/SelectVideoBtn.tsx +++ b/src/view/com/composer/videos/SelectVideoBtn.tsx @@ -15,10 +15,11 @@ import {useVideoLibraryPermission} from '#/lib/hooks/usePermissions' import {getHostnameFromUrl} from '#/lib/strings/url-helpers' import {isWeb} from '#/platform/detection' import {isNative} from '#/platform/detection' -import {useModalControls} from '#/state/modals' import {useSession} from '#/state/session' import {atoms as a, useTheme} from '#/alf' import {Button} from '#/components/Button' +import {useDialogControl} from '#/components/Dialog' +import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog' import {VideoClip_Stroke2_Corner0_Rounded as VideoClipIcon} from '#/components/icons/VideoClip' import * as Prompt from '#/components/Prompt' @@ -121,26 +122,24 @@ export function SelectVideoBtn({onSelectVideo, disabled, setError}: Props) { function VerifyEmailPrompt({control}: {control: Prompt.PromptControlProps}) { const {_} = useLingui() - const {openModal} = useModalControls() + const verifyEmailDialogControl = useDialogControl() return ( - <Prompt.Basic - control={control} - title={_(msg`Verified email required`)} - description={_( - msg`To upload videos to Bluesky, you must first verify your email.`, - )} - confirmButtonCta={_(msg`Verify now`)} - confirmButtonColor="primary" - onConfirm={() => { - control.close(() => { - openModal({ - name: 'verify-email', - showReminder: false, - }) - }) - }} - /> + <> + <Prompt.Basic + control={control} + title={_(msg`Verified email required`)} + description={_( + msg`To upload videos to Bluesky, you must first verify your email.`, + )} + confirmButtonCta={_(msg`Verify now`)} + confirmButtonColor="primary" + onConfirm={() => { + verifyEmailDialogControl.open() + }} + /> + <VerifyEmailDialog control={verifyEmailDialogControl} /> + </> ) } diff --git a/src/view/screens/Settings/index.tsx b/src/view/screens/Settings/index.tsx index a2b767097..a40cc4f26 100644 --- a/src/view/screens/Settings/index.tsx +++ b/src/view/screens/Settings/index.tsx @@ -56,6 +56,7 @@ import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateA import {atoms as a, useTheme} from '#/alf' import {useDialogControl} from '#/components/Dialog' import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' +import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog' import {Email2FAToggle} from './Email2FAToggle' import {ExportCarDialog} from './ExportCarDialog' @@ -927,7 +928,7 @@ function EmailConfirmationNotice() { const palInverted = usePalette('inverted') const {_} = useLingui() const {isMobile} = useWebMediaQueries() - const {openModal} = useModalControls() + const verifyEmailDialogControl = useDialogControl() return ( <View style={{marginBottom: 20}}> @@ -959,7 +960,7 @@ function EmailConfirmationNotice() { accessibilityRole="button" accessibilityLabel={_(msg`Verify my email`)} accessibilityHint={_(msg`Opens modal for email verification`)} - onPress={() => openModal({name: 'verify-email'})}> + onPress={() => verifyEmailDialogControl.open()}> <FontAwesomeIcon icon="envelope" color={palInverted.colors.text} @@ -974,6 +975,7 @@ function EmailConfirmationNotice() { <Trans>Protect your account by verifying your email.</Trans> </Text> </View> + <VerifyEmailDialog control={verifyEmailDialogControl} /> </View> ) } |