diff --git a/src/components/dialogs/ChangeEmailDialog.tsx b/src/components/dialogs/ChangeEmailDialog.tsx
new file mode 100644
index 0000000000..433e4289b9
--- /dev/null
+++ b/src/components/dialogs/ChangeEmailDialog.tsx
@@ -0,0 +1,271 @@
+import React, {useState} 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 {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 {Loader} from '#/components/Loader'
+import {Text} from '#/components/Typography'
+
+export function ChangeEmailDialog({
+ control,
+ verifyEmailControl,
+}: {
+ control: Dialog.DialogControlProps
+ verifyEmailControl: Dialog.DialogControlProps
+}) {
+ return (
+
+
+
+
+ )
+}
+
+export function Inner({
+ verifyEmailControl,
+}: {
+ verifyEmailControl: Dialog.DialogControlProps
+}) {
+ const {_} = useLingui()
+ const {currentAccount} = useSession()
+ const agent = useAgent()
+ const control = Dialog.useDialogContext()
+ const {gtMobile} = useBreakpoints()
+
+ const [currentStep, setCurrentStep] = React.useState<
+ 'StepOne' | 'StepTwo' | 'StepThree'
+ >('StepOne')
+ const [email, setEmail] = useState(currentAccount?.email || '')
+ const [confirmationCode, setConfirmationCode] = useState('')
+ const [isProcessing, setIsProcessing] = useState(false)
+ const [error, setError] = useState('')
+
+ const currentEmail = currentAccount?.email || '(no email)'
+ const uiStrings = {
+ StepOne: {
+ title: _(msg`Change Your Email`),
+ message: '',
+ },
+ StepTwo: {
+ title: _(msg`Security Step Required`),
+ message: _(
+ msg` An email has been sent to your previous address, ${currentEmail}. It includes a confirmation code which you can enter below.`,
+ ),
+ },
+ StepThree: {
+ title: _(msg`Email Updated!`),
+ message: _(
+ msg`Your email has been updated but not verified. As a next step, please verify your new email.`,
+ ),
+ },
+ }
+
+ const onRequestChange = async () => {
+ if (email === currentAccount?.email) {
+ setError(
+ _(
+ msg`The email address you entered is the same as your current email address.`,
+ ),
+ )
+ return
+ }
+ setError('')
+ setIsProcessing(true)
+ try {
+ const res = await agent.com.atproto.server.requestEmailUpdate()
+ if (res.data.tokenRequired) {
+ setCurrentStep('StepTwo')
+ } else {
+ await agent.com.atproto.server.updateEmail({email: email.trim()})
+ await agent.resumeSession(agent.session!)
+ setCurrentStep('StepThree')
+ }
+ } catch (e) {
+ let err = cleanError(String(e))
+ // TEMP
+ // while rollout is occuring, we're giving a temporary error message
+ // you can remove this any time after Oct2023
+ // -prf
+ if (err === 'email must be confirmed (temporary)') {
+ err = _(
+ msg`Please confirm your email before changing it. This is a temporary requirement while email-updating tools are added, and it will soon be removed.`,
+ )
+ }
+ setError(err)
+ } finally {
+ setIsProcessing(false)
+ }
+ }
+
+ const onConfirm = async () => {
+ setError('')
+ setIsProcessing(true)
+ try {
+ await agent.com.atproto.server.updateEmail({
+ email: email.trim(),
+ token: confirmationCode.trim(),
+ })
+ await agent.resumeSession(agent.session!)
+ setCurrentStep('StepThree')
+ } catch (e) {
+ setError(cleanError(String(e)))
+ } finally {
+ setIsProcessing(false)
+ }
+ }
+
+ const onVerify = async () => {
+ control.close(() => {
+ verifyEmailControl.open()
+ })
+ }
+
+ return (
+
+
+
+
+
+ {uiStrings[currentStep].title}
+
+ {error ? (
+
+
+
+ ) : null}
+ {currentStep === 'StepOne' ? (
+
+
+ Enter your new email address below.
+
+
+
+
+
+ ) : (
+
+ {uiStrings[currentStep].message}
+
+ )}
+
+ {currentStep === 'StepTwo' ? (
+
+
+ Confirmation Code
+
+
+
+
+
+ ) : null}
+
+ {currentStep === 'StepOne' ? (
+ <>
+
+
+ >
+ ) : currentStep === 'StepTwo' ? (
+ <>
+
+
+ >
+ ) : currentStep === 'StepThree' ? (
+ <>
+
+
+ >
+ ) : null}
+
+
+
+ )
+}
diff --git a/src/components/dialogs/VerifyEmailDialog.tsx b/src/components/dialogs/VerifyEmailDialog.tsx
index ced9171ce3..1e01a8e3c5 100644
--- a/src/components/dialogs/VerifyEmailDialog.tsx
+++ b/src/components/dialogs/VerifyEmailDialog.tsx
@@ -5,7 +5,6 @@ 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'
@@ -21,11 +20,13 @@ export function VerifyEmailDialog({
onCloseWithoutVerifying,
onCloseAfterVerifying,
reasonText,
+ changeEmailControl,
}: {
control: Dialog.DialogControlProps
onCloseWithoutVerifying?: () => void
onCloseAfterVerifying?: () => void
reasonText?: string
+ changeEmailControl: Dialog.DialogControlProps
}) {
const agent = useAgent()
@@ -50,27 +51,27 @@ export function VerifyEmailDialog({
}}>
)
}
export function Inner({
- control,
setDidVerify,
reasonText,
+ changeEmailControl,
}: {
- control: Dialog.DialogControlProps
setDidVerify: (value: boolean) => void
reasonText?: string
+ changeEmailControl: Dialog.DialogControlProps
}) {
+ const control = Dialog.useDialogContext()
const {_} = useLingui()
const {currentAccount} = useSession()
const agent = useAgent()
- const {openModal} = useModalControls()
const {gtMobile} = useBreakpoints()
const [currentStep, setCurrentStep] = React.useState<
@@ -164,7 +165,7 @@ export function Inner({
onPress={e => {
e.preventDefault()
control.close(() => {
- openModal({name: 'change-email'})
+ changeEmailControl.open()
})
return false
}}>
@@ -189,7 +190,7 @@ export function Inner({
onPress={e => {
e.preventDefault()
control.close(() => {
- openModal({name: 'change-email'})
+ changeEmailControl.open()
})
return false
}}>
@@ -236,14 +237,14 @@ export function Inner({
) : null}
>
diff --git a/src/screens/Settings/AccountSettings.tsx b/src/screens/Settings/AccountSettings.tsx
index a69c5cdd3d..7c50bd8dff 100644
--- a/src/screens/Settings/AccountSettings.tsx
+++ b/src/screens/Settings/AccountSettings.tsx
@@ -9,6 +9,7 @@ import * as SettingsList from '#/screens/Settings/components/SettingsList'
import {atoms as a, useTheme} from '#/alf'
import {useDialogControl} from '#/components/Dialog'
import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
+import {ChangeEmailDialog} from '#/components/dialogs/ChangeEmailDialog'
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
import {At_Stroke2_Corner2_Rounded as AtIcon} from '#/components/icons/At'
import {BirthdayCake_Stroke2_Corner2_Rounded as BirthdayCakeIcon} from '#/components/icons/BirthdayCake'
@@ -31,6 +32,7 @@ export function AccountSettingsScreen({}: Props) {
const {currentAccount} = useSession()
const {openModal} = useModalControls()
const verifyEmailControl = useDialogControl()
+ const changeEmailControl = useDialogControl()
const birthdayControl = useDialogControl()
const changeHandleControl = useDialogControl()
const exportCarControl = useDialogControl()
@@ -95,7 +97,7 @@ export function AccountSettingsScreen({}: Props) {
)}
openModal({name: 'change-email'})}>
+ onPress={() => changeEmailControl.open()}>
Change email
@@ -165,7 +167,14 @@ export function AccountSettingsScreen({}: Props) {
-
+
+
diff --git a/src/screens/Settings/components/Email2FAToggle.tsx b/src/screens/Settings/components/Email2FAToggle.tsx
index 434792fb48..810e843d24 100644
--- a/src/screens/Settings/components/Email2FAToggle.tsx
+++ b/src/screens/Settings/components/Email2FAToggle.tsx
@@ -4,6 +4,7 @@ import {useLingui} from '@lingui/react'
import {useAgent, useSession} from '#/state/session'
import {useDialogControl} from '#/components/Dialog'
+import {ChangeEmailDialog} from '#/components/dialogs/ChangeEmailDialog'
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
import * as Prompt from '#/components/Prompt'
import {DisableEmail2FADialog} from './DisableEmail2FADialog'
@@ -15,6 +16,7 @@ export function Email2FAToggle() {
const disableDialogControl = useDialogControl()
const enableDialogControl = useDialogControl()
const verifyEmailDialogControl = useDialogControl()
+ const changeEmailDialogControl = useDialogControl()
const agent = useAgent()
const enableEmailAuthFactor = React.useCallback(async () => {
@@ -59,11 +61,16 @@ export function Email2FAToggle() {
/>
+