about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorHailey <me@haileyok.com>2024-10-10 15:02:36 -0700
committerGitHub <noreply@github.com>2024-10-10 15:02:36 -0700
commit2ebe8f435bf573932c14c06aaa2dc3b55ad363c6 (patch)
treeb99eca3a67ef69d9c56aa830e154d89e7b816b39 /src
parent912b4450b9489b5b7a60ce72323bd083e2a3db46 (diff)
downloadvoidsky-2ebe8f435bf573932c14c06aaa2dc3b55ad363c6.tar.zst
Update the email verification dialog (#5663)
Diffstat (limited to 'src')
-rw-r--r--src/components/dialogs/VerifyEmailDialog.tsx254
-rw-r--r--src/view/com/composer/videos/SelectVideoBtn.tsx37
-rw-r--r--src/view/screens/Settings/index.tsx6
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>
   )
 }