use new verify email dialog for reminder

This commit is contained in:
Samuel Newman
2024-10-24 13:47:11 +03:00
parent 62472ee738
commit c2cd76e21a
3 changed files with 119 additions and 48 deletions
+20 -16
View File
@@ -33,7 +33,6 @@ import {attachRouteToLogEvents, logEvent} from '#/lib/statsig/statsig'
import {bskyTitle} from '#/lib/strings/headings' import {bskyTitle} from '#/lib/strings/headings'
import {logger} from '#/logger' import {logger} from '#/logger'
import {isNative, isWeb} from '#/platform/detection' import {isNative, isWeb} from '#/platform/detection'
import {useModalControls} from '#/state/modals'
import {useUnreadNotifications} from '#/state/queries/notifications/unread' import {useUnreadNotifications} from '#/state/queries/notifications/unread'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import { import {
@@ -80,33 +79,35 @@ import {ProfileFeedScreen} from '#/screens/Profile/ProfileFeed'
import {ProfileFollowersScreen} from '#/screens/Profile/ProfileFollowers' import {ProfileFollowersScreen} from '#/screens/Profile/ProfileFollowers'
import {ProfileFollowsScreen} from '#/screens/Profile/ProfileFollows' import {ProfileFollowsScreen} from '#/screens/Profile/ProfileFollows'
import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy' import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy'
import {ProfileSearchScreen} from '#/screens/Profile/ProfileSearch'
import {SearchScreen} from '#/screens/Search' import {SearchScreen} from '#/screens/Search'
import {AboutSettingsScreen} from '#/screens/Settings/AboutSettings'
import {AccessibilitySettingsScreen} from '#/screens/Settings/AccessibilitySettings'
import {AccountSettingsScreen} from '#/screens/Settings/AccountSettings'
import {AppearanceSettingsScreen} from '#/screens/Settings/AppearanceSettings' import {AppearanceSettingsScreen} from '#/screens/Settings/AppearanceSettings'
import {AppIconSettingsScreen} from '#/screens/Settings/AppIconSettings' import {AppIconSettingsScreen} from '#/screens/Settings/AppIconSettings'
import {AppPasswordsScreen} from '#/screens/Settings/AppPasswords'
import {ContentAndMediaSettingsScreen} from '#/screens/Settings/ContentAndMediaSettings'
import {ExternalMediaPreferencesScreen} from '#/screens/Settings/ExternalMediaPreferences'
import {FollowingFeedPreferencesScreen} from '#/screens/Settings/FollowingFeedPreferences'
import {LanguageSettingsScreen} from '#/screens/Settings/LanguageSettings'
import {NotificationSettingsScreen} from '#/screens/Settings/NotificationSettings' import {NotificationSettingsScreen} from '#/screens/Settings/NotificationSettings'
import {PrivacyAndSecuritySettingsScreen} from '#/screens/Settings/PrivacyAndSecuritySettings'
import {SettingsScreen} from '#/screens/Settings/Settings'
import {SettingsInterests} from '#/screens/Settings/SettingsInterests' import {SettingsInterests} from '#/screens/Settings/SettingsInterests'
import {ThreadPreferencesScreen} from '#/screens/Settings/ThreadPreferences'
import { import {
StarterPackScreen, StarterPackScreen,
StarterPackScreenShort, StarterPackScreenShort,
} from '#/screens/StarterPack/StarterPackScreen' } from '#/screens/StarterPack/StarterPackScreen'
import {Wizard} from '#/screens/StarterPack/Wizard' import {Wizard} from '#/screens/StarterPack/Wizard'
import TopicScreen from '#/screens/Topic'
import {VideoFeed} from '#/screens/VideoFeed' import {VideoFeed} from '#/screens/VideoFeed'
import {useTheme} from '#/alf' import {useTheme} from '#/alf'
import {useDialogControl} from '#/components/Dialog'
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
import {router} from '#/routes' import {router} from '#/routes'
import {Referrer} from '../modules/expo-bluesky-swiss-army' import {Referrer} from '../modules/expo-bluesky-swiss-army'
import {ProfileSearchScreen} from './screens/Profile/ProfileSearch'
import {AboutSettingsScreen} from './screens/Settings/AboutSettings'
import {AccessibilitySettingsScreen} from './screens/Settings/AccessibilitySettings'
import {AccountSettingsScreen} from './screens/Settings/AccountSettings'
import {AppPasswordsScreen} from './screens/Settings/AppPasswords'
import {ContentAndMediaSettingsScreen} from './screens/Settings/ContentAndMediaSettings'
import {ExternalMediaPreferencesScreen} from './screens/Settings/ExternalMediaPreferences'
import {FollowingFeedPreferencesScreen} from './screens/Settings/FollowingFeedPreferences'
import {LanguageSettingsScreen} from './screens/Settings/LanguageSettings'
import {PrivacyAndSecuritySettingsScreen} from './screens/Settings/PrivacyAndSecuritySettings'
import {SettingsScreen} from './screens/Settings/Settings'
import {ThreadPreferencesScreen} from './screens/Settings/ThreadPreferences'
import TopicScreen from './screens/Topic'
const navigationRef = createNavigationContainerRef<AllNavigatorParams>() const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
@@ -736,18 +737,19 @@ const LINKING = {
function RoutesContainer({children}: React.PropsWithChildren<{}>) { function RoutesContainer({children}: React.PropsWithChildren<{}>) {
const theme = useColorSchemeStyle(DefaultTheme, DarkTheme) const theme = useColorSchemeStyle(DefaultTheme, DarkTheme)
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {openModal} = useModalControls()
const prevLoggedRouteName = React.useRef<string | undefined>(undefined) const prevLoggedRouteName = React.useRef<string | undefined>(undefined)
const verifyEmailDialogControl = useDialogControl()
function onReady() { function onReady() {
prevLoggedRouteName.current = getCurrentRouteName() prevLoggedRouteName.current = getCurrentRouteName()
if (currentAccount && shouldRequestEmailConfirmation(currentAccount)) { if (currentAccount && shouldRequestEmailConfirmation(currentAccount)) {
openModal({name: 'verify-email', showReminder: true}) verifyEmailDialogControl.open()
snoozeEmailConfirmationPrompt() snoozeEmailConfirmationPrompt()
} }
} }
return ( return (
<>
<NavigationContainer <NavigationContainer
ref={navigationRef} ref={navigationRef}
linking={LINKING} linking={LINKING}
@@ -766,6 +768,8 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
}}> }}>
{children} {children}
</NavigationContainer> </NavigationContainer>
<VerifyEmailDialog control={verifyEmailDialogControl} reminder />
</>
) )
} }
+63 -14
View File
@@ -1,4 +1,4 @@
import React from 'react' import {useState} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -7,10 +7,11 @@ import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useAgent, useSession} from '#/state/session' import {useAgent, useSession} from '#/state/session'
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
import {atoms as a, useBreakpoints, web} from '#/alf' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
import * as TextField from '#/components/forms/TextField' import * as TextField from '#/components/forms/TextField'
import {Envelope_Filled_Stroke2_Corner0_Rounded as EnvelopeIcon} from '#/components/icons/Envelope'
import {InlineLinkText} from '#/components/Link' import {InlineLinkText} from '#/components/Link'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
@@ -22,6 +23,7 @@ export function VerifyEmailDialog({
onCloseAfterVerifying, onCloseAfterVerifying,
reasonText, reasonText,
changeEmailControl, changeEmailControl,
reminder,
}: { }: {
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
onCloseWithoutVerifying?: () => void onCloseWithoutVerifying?: () => void
@@ -33,11 +35,12 @@ export function VerifyEmailDialog({
* helps reduce duplication, since these dialogs are often used together. * helps reduce duplication, since these dialogs are often used together.
*/ */
changeEmailControl?: Dialog.DialogControlProps changeEmailControl?: Dialog.DialogControlProps
reminder?: boolean
}) { }) {
const agent = useAgent() const agent = useAgent()
const fallbackChangeEmailControl = Dialog.useDialogControl() const fallbackChangeEmailControl = Dialog.useDialogControl()
const [didVerify, setDidVerify] = React.useState(false) const [didVerify, setDidVerify] = useState(false)
return ( return (
<> <>
@@ -62,6 +65,7 @@ export function VerifyEmailDialog({
setDidVerify={setDidVerify} setDidVerify={setDidVerify}
reasonText={reasonText} reasonText={reasonText}
changeEmailControl={changeEmailControl ?? fallbackChangeEmailControl} changeEmailControl={changeEmailControl ?? fallbackChangeEmailControl}
reminder={reminder}
/> />
</Dialog.Outer> </Dialog.Outer>
{!changeEmailControl && ( {!changeEmailControl && (
@@ -78,25 +82,34 @@ export function Inner({
setDidVerify, setDidVerify,
reasonText, reasonText,
changeEmailControl, changeEmailControl,
reminder,
}: { }: {
setDidVerify: (value: boolean) => void setDidVerify: (value: boolean) => void
reasonText?: string reasonText?: string
changeEmailControl: Dialog.DialogControlProps changeEmailControl: Dialog.DialogControlProps
reminder?: boolean
}) { }) {
const control = Dialog.useDialogContext() const control = Dialog.useDialogContext()
const {_} = useLingui() const {_} = useLingui()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const agent = useAgent() const agent = useAgent()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const t = useTheme()
const [currentStep, setCurrentStep] = React.useState< const [currentStep, setCurrentStep] = useState<
'StepOne' | 'StepTwo' | 'StepThree' 'Reminder' | 'StepOne' | 'StepTwo' | 'StepThree'
>('StepOne') >(reminder ? 'Reminder' : 'StepOne')
const [confirmationCode, setConfirmationCode] = React.useState('') const [confirmationCode, setConfirmationCode] = useState('')
const [isProcessing, setIsProcessing] = React.useState(false) const [isProcessing, setIsProcessing] = useState(false)
const [error, setError] = React.useState('') const [error, setError] = useState('')
const uiStrings = { const uiStrings = {
Reminder: {
title: _(msg`Please Verify Your Email`),
message: _(
msg`Your email has not yet been verified. This is an important security step which we recommend.`,
),
},
StepOne: { StepOne: {
title: _(msg`Verify Your Email`), title: _(msg`Verify Your Email`),
message: '', message: '',
@@ -151,6 +164,17 @@ export function Inner({
style={web({maxWidth: 450})}> style={web({maxWidth: 450})}>
<Dialog.Close /> <Dialog.Close />
<View style={[a.gap_xl]}> <View style={[a.gap_xl]}>
{currentStep === 'Reminder' && (
<View
style={[
a.rounded_sm,
a.align_center,
a.justify_center,
{height: 150, backgroundColor: t.palette.primary_500},
]}>
<EnvelopeIcon width={64} fill="white" />
</View>
)}
<View style={[a.gap_sm]}> <View style={[a.gap_sm]}>
<Text style={[a.font_heavy, a.text_2xl]}> <Text style={[a.font_heavy, a.text_2xl]}>
{uiStrings[currentStep].title} {uiStrings[currentStep].title}
@@ -233,7 +257,32 @@ export function Inner({
</View> </View>
) : null} ) : null}
<View style={[a.gap_sm, gtMobile && [a.flex_row_reverse, a.ml_auto]]}> <View style={[a.gap_sm, gtMobile && [a.flex_row_reverse, a.ml_auto]]}>
{currentStep === 'StepOne' ? ( {currentStep === 'Reminder' ? (
<>
<Button
label={_(msg`Get started`)}
variant="solid"
color="primary"
size="large"
onPress={() => setCurrentStep('StepOne')}>
<ButtonText>
<Trans>Get started</Trans>
</ButtonText>
</Button>
<Button
label={_(msg`Maybe layer`)}
accessibilityHint={_(msg`Snooze reminder`)}
variant="solid"
color="secondary"
size="large"
disabled={isProcessing}
onPress={() => control.close()}>
<ButtonText>
<Trans>Maybe later</Trans>
</ButtonText>
</Button>
</>
) : currentStep === 'StepOne' ? (
<> <>
<Button <Button
label={_(msg`Send confirmation email`)} label={_(msg`Send confirmation email`)}
@@ -243,7 +292,7 @@ export function Inner({
disabled={isProcessing} disabled={isProcessing}
onPress={onSendEmail}> onPress={onSendEmail}>
<ButtonText> <ButtonText>
<Trans>Send Confirmation</Trans> <Trans>Send confirmation</Trans>
</ButtonText> </ButtonText>
{isProcessing ? ( {isProcessing ? (
<Loader size="sm" style={[{color: 'white'}]} /> <Loader size="sm" style={[{color: 'white'}]} />
@@ -278,7 +327,7 @@ export function Inner({
) : null} ) : null}
</Button> </Button>
<Button <Button
label={_(msg`Resend Email`)} label={_(msg`Resend email`)}
variant="solid" variant="solid"
color="secondary" color="secondary"
size="large" size="large"
@@ -288,13 +337,13 @@ export function Inner({
setCurrentStep('StepOne') setCurrentStep('StepOne')
}}> }}>
<ButtonText> <ButtonText>
<Trans>Resend Email</Trans> <Trans>Resend email</Trans>
</ButtonText> </ButtonText>
</Button> </Button>
</> </>
) : currentStep === 'StepThree' ? ( ) : currentStep === 'StepThree' ? (
<Button <Button
label={_(msg`Confirm`)} label={_(msg`Close`)}
variant="solid" variant="solid"
color="primary" color="primary"
size="large" size="large"
+18
View File
@@ -18,6 +18,7 @@ import {
import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles' import {sanitizeHandle} from '#/lib/strings/handles'
import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileShadow} from '#/state/cache/profile-shadow'
import * as persisted from '#/state/persisted'
import {clearStorage} from '#/state/persisted' import {clearStorage} from '#/state/persisted'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration' import {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration'
@@ -359,6 +360,16 @@ function DevOptions() {
Toast.show(_(msg`Storage cleared, you need to restart the app now.`)) Toast.show(_(msg`Storage cleared, you need to restart the app now.`))
} }
const onPressUnsnoozeReminder = () => {
const lastEmailConfirm = new Date()
// wind back 3 days
lastEmailConfirm.setDate(lastEmailConfirm.getDate() - 3)
persisted.write('reminders', {
...persisted.get('reminders'),
lastEmailConfirm: lastEmailConfirm.toISOString(),
})
}
return ( return (
<> <>
<SettingsList.PressableItem <SettingsList.PressableItem
@@ -396,6 +407,13 @@ function DevOptions() {
<Trans>Reset onboarding state</Trans> <Trans>Reset onboarding state</Trans>
</SettingsList.ItemText> </SettingsList.ItemText>
</SettingsList.PressableItem> </SettingsList.PressableItem>
<SettingsList.PressableItem
onPress={onPressUnsnoozeReminder}
label={_(msg`Unsnooze email reminder`)}>
<SettingsList.ItemText>
<Trans>Unsnooze email reminder</Trans>
</SettingsList.ItemText>
</SettingsList.PressableItem>
<SettingsList.PressableItem <SettingsList.PressableItem
onPress={() => clearAllStorage()} onPress={() => clearAllStorage()}
label={_(msg`Clear all storage data`)}> label={_(msg`Clear all storage data`)}>