Add email verification prompts throughout the app (#6174)

This commit is contained in:
Hailey
2024-11-12 11:18:53 -08:00
committed by GitHub
parent dd8d14e133
commit 427f3a8bd7
11 changed files with 265 additions and 46 deletions
@@ -14,6 +14,7 @@ import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query'
import {useGenerateStarterPackMutation} from '#/lib/generate-starterpack' import {useGenerateStarterPackMutation} from '#/lib/generate-starterpack'
import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset'
import {useEmail} from '#/lib/hooks/useEmail'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {NavigationProp} from '#/lib/routes/types' import {NavigationProp} from '#/lib/routes/types'
import {parseStarterPackUri} from '#/lib/strings/starter-pack' import {parseStarterPackUri} from '#/lib/strings/starter-pack'
@@ -27,6 +28,7 @@ import {LinearGradientBackground} from '#/components/LinearGradientBackground'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import {Default as StarterPackCard} from '#/components/StarterPack/StarterPackCard' import {Default as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
import {VerifyEmailDialog} from '../dialogs/VerifyEmailDialog'
import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '../icons/Plus' import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '../icons/Plus'
interface SectionRef { interface SectionRef {
@@ -186,6 +188,9 @@ function Empty() {
const followersDialogControl = useDialogControl() const followersDialogControl = useDialogControl()
const errorDialogControl = useDialogControl() const errorDialogControl = useDialogControl()
const {needsEmailVerification} = useEmail()
const verifyEmailControl = useDialogControl()
const [isGenerating, setIsGenerating] = React.useState(false) const [isGenerating, setIsGenerating] = React.useState(false)
const {mutate: generateStarterPack} = useGenerateStarterPackMutation({ const {mutate: generateStarterPack} = useGenerateStarterPackMutation({
@@ -249,7 +254,13 @@ function Empty() {
color="primary" color="primary"
size="small" size="small"
disabled={isGenerating} disabled={isGenerating}
onPress={confirmDialogControl.open} onPress={() => {
if (needsEmailVerification) {
verifyEmailControl.open()
} else {
confirmDialogControl.open()
}
}}
style={{backgroundColor: 'transparent'}}> style={{backgroundColor: 'transparent'}}>
<ButtonText style={{color: 'white'}}> <ButtonText style={{color: 'white'}}>
<Trans>Make one for me</Trans> <Trans>Make one for me</Trans>
@@ -262,7 +273,13 @@ function Empty() {
color="primary" color="primary"
size="small" size="small"
disabled={isGenerating} disabled={isGenerating}
onPress={() => navigation.navigate('StarterPackWizard')} onPress={() => {
if (needsEmailVerification) {
verifyEmailControl.open()
} else {
navigation.navigate('StarterPackWizard')
}
}}
style={{ style={{
backgroundColor: 'white', backgroundColor: 'white',
borderColor: 'white', borderColor: 'white',
@@ -318,6 +335,12 @@ function Empty() {
onConfirm={generate} onConfirm={generate}
confirmButtonCta={_(msg`Retry`)} confirmButtonCta={_(msg`Retry`)}
/> />
<VerifyEmailDialog
reasonText={_(
msg`Before creating a starter pack, you must first verify your email.`,
)}
control={verifyEmailControl}
/>
</LinearGradientBackground> </LinearGradientBackground>
) )
} }
+41 -21
View File
@@ -18,8 +18,14 @@ import {Text} from '#/components/Typography'
export function VerifyEmailDialog({ export function VerifyEmailDialog({
control, control,
onCloseWithoutVerifying,
onCloseAfterVerifying,
reasonText,
}: { }: {
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
onCloseWithoutVerifying?: () => void
onCloseAfterVerifying?: () => void
reasonText?: string
}) { }) {
const agent = useAgent() const agent = useAgent()
@@ -30,18 +36,24 @@ export function VerifyEmailDialog({
control={control} control={control}
onClose={async () => { onClose={async () => {
if (!didVerify) { if (!didVerify) {
onCloseWithoutVerifying?.()
return return
} }
try { try {
await agent.resumeSession(agent.session!) await agent.resumeSession(agent.session!)
onCloseAfterVerifying?.()
} catch (e: unknown) { } catch (e: unknown) {
logger.error(String(e)) logger.error(String(e))
return return
} }
}}> }}>
<Dialog.Handle /> <Dialog.Handle />
<Inner control={control} setDidVerify={setDidVerify} /> <Inner
control={control}
setDidVerify={setDidVerify}
reasonText={reasonText}
/>
</Dialog.Outer> </Dialog.Outer>
) )
} }
@@ -49,9 +61,11 @@ export function VerifyEmailDialog({
export function Inner({ export function Inner({
control, control,
setDidVerify, setDidVerify,
reasonText,
}: { }: {
control: Dialog.DialogControlProps control: Dialog.DialogControlProps
setDidVerify: (value: boolean) => void setDidVerify: (value: boolean) => void
reasonText?: string
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const {currentAccount} = useSession() const {currentAccount} = useSession()
@@ -135,26 +149,32 @@ export function Inner({
<Text style={[a.text_md, a.leading_snug]}> <Text style={[a.text_md, a.leading_snug]}>
{currentStep === 'StepOne' ? ( {currentStep === 'StepOne' ? (
<> <>
<Trans> {!reasonText ? (
You'll receive an email at{' '} <>
<Text style={[a.text_md, a.leading_snug, a.font_bold]}> <Trans>
{currentAccount?.email} You'll receive an email at{' '}
</Text>{' '} <Text style={[a.text_md, a.leading_snug, a.font_bold]}>
to verify it's you. {currentAccount?.email}
</Trans>{' '} </Text>{' '}
<InlineLinkText to verify it's you.
to="#" </Trans>{' '}
label={_(msg`Change email address`)} <InlineLinkText
style={[a.text_md, a.leading_snug]} to="#"
onPress={e => { label={_(msg`Change email address`)}
e.preventDefault() style={[a.text_md, a.leading_snug]}
control.close(() => { onPress={e => {
openModal({name: 'change-email'}) e.preventDefault()
}) control.close(() => {
return false openModal({name: 'change-email'})
}}> })
<Trans>Need to change it?</Trans> return false
</InlineLinkText> }}>
<Trans>Need to change it?</Trans>
</InlineLinkText>
</>
) : (
reasonText
)}
</> </>
) : ( ) : (
uiStrings[currentStep].message uiStrings[currentStep].message
+41 -15
View File
@@ -3,14 +3,18 @@ import {View} from 'react-native'
import {AppBskyActorDefs} from '@atproto/api' import {AppBskyActorDefs} from '@atproto/api'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {useEmail} from '#/lib/hooks/useEmail'
import {NavigationProp} from '#/lib/routes/types'
import {logEvent} from '#/lib/statsig/statsig' import {logEvent} from '#/lib/statsig/statsig'
import {useMaybeConvoForUser} from '#/state/queries/messages/get-convo-for-members' import {useMaybeConvoForUser} from '#/state/queries/messages/get-convo-for-members'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {ButtonIcon} from '#/components/Button' import {Button, ButtonIcon} from '#/components/Button'
import {canBeMessaged} from '#/components/dms/util' import {canBeMessaged} from '#/components/dms/util'
import {Message_Stroke2_Corner0_Rounded as Message} from '#/components/icons/Message' import {Message_Stroke2_Corner0_Rounded as Message} from '#/components/icons/Message'
import {Link} from '#/components/Link' import {useDialogControl} from '../Dialog'
import {VerifyEmailDialog} from '../dialogs/VerifyEmailDialog'
export function MessageProfileButton({ export function MessageProfileButton({
profile, profile,
@@ -19,15 +23,29 @@ export function MessageProfileButton({
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
const navigation = useNavigation<NavigationProp>()
const {needsEmailVerification} = useEmail()
const verifyEmailControl = useDialogControl()
const {data: convo, isPending} = useMaybeConvoForUser(profile.did) const {data: convo, isPending} = useMaybeConvoForUser(profile.did)
const onPress = React.useCallback(() => { const onPress = React.useCallback(() => {
if (!convo?.id) {
return
}
if (needsEmailVerification) {
verifyEmailControl.open()
return
}
if (convo && !convo.lastMessage) { if (convo && !convo.lastMessage) {
logEvent('chat:create', {logContext: 'ProfileHeader'}) logEvent('chat:create', {logContext: 'ProfileHeader'})
} }
logEvent('chat:open', {logContext: 'ProfileHeader'}) logEvent('chat:open', {logContext: 'ProfileHeader'})
}, [convo])
navigation.navigate('MessagesConversation', {conversation: convo.id})
}, [needsEmailVerification, verifyEmailControl, convo, navigation])
if (isPending) { if (isPending) {
// show pending state based on declaration // show pending state based on declaration
@@ -53,18 +71,26 @@ export function MessageProfileButton({
if (convo) { if (convo) {
return ( return (
<Link <>
testID="dmBtn" <Button
size="small" accessibilityRole="button"
color="secondary" testID="dmBtn"
variant="solid" size="small"
shape="round" color="secondary"
label={_(msg`Message ${profile.handle}`)} variant="solid"
to={`/messages/${convo.id}`} shape="round"
style={[a.justify_center]} label={_(msg`Message ${profile.handle}`)}
onPress={onPress}> style={[a.justify_center]}
<ButtonIcon icon={Message} size="md" /> onPress={onPress}>
</Link> <ButtonIcon icon={Message} size="md" />
</Button>
<VerifyEmailDialog
reasonText={_(
msg`Before you may message another user, you must first verify your email.`,
)}
control={verifyEmailControl}
/>
</>
) )
} else { } else {
return null return null
+19 -1
View File
@@ -2,6 +2,7 @@ import React, {useCallback} from 'react'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useEmail} from '#/lib/hooks/useEmail'
import {logEvent} from '#/lib/statsig/statsig' import {logEvent} from '#/lib/statsig/statsig'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members' import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
@@ -9,6 +10,8 @@ import {FAB} from '#/view/com/util/fab/FAB'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
import {useTheme} from '#/alf' import {useTheme} from '#/alf'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
import {useDialogControl} from '#/components/Dialog'
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {SearchablePeopleList} from './SearchablePeopleList' import {SearchablePeopleList} from './SearchablePeopleList'
@@ -21,6 +24,8 @@ export function NewChat({
}) { }) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const {needsEmailVerification} = useEmail()
const verifyEmailControl = useDialogControl()
const {mutate: createChat} = useGetConvoForMembers({ const {mutate: createChat} = useGetConvoForMembers({
onSuccess: data => { onSuccess: data => {
@@ -48,7 +53,13 @@ export function NewChat({
<> <>
<FAB <FAB
testID="newChatFAB" testID="newChatFAB"
onPress={control.open} onPress={() => {
if (needsEmailVerification) {
verifyEmailControl.open()
} else {
control.open()
}
}}
icon={<Plus size="lg" fill={t.palette.white} />} icon={<Plus size="lg" fill={t.palette.white} />}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`New chat`)} accessibilityLabel={_(msg`New chat`)}
@@ -62,6 +73,13 @@ export function NewChat({
onSelectChat={onCreateChat} onSelectChat={onCreateChat}
/> />
</Dialog.Outer> </Dialog.Outer>
<VerifyEmailDialog
reasonText={_(
msg`Before you may message another user, you must first verify your email.`,
)}
control={verifyEmailControl}
/>
</> </>
) )
} }
+19
View File
@@ -0,0 +1,19 @@
import {useServiceConfigQuery} from '#/state/queries/email-verification-required'
import {useSession} from '#/state/session'
import {BSKY_SERVICE} from '../constants'
import {getHostnameFromUrl} from '../strings/url-helpers'
export function useEmail() {
const {currentAccount} = useSession()
const {data: serviceConfig} = useServiceConfigQuery()
const isSelfHost =
serviceConfig?.checkEmailConfirmed &&
currentAccount &&
getHostnameFromUrl(currentAccount.service) !==
getHostnameFromUrl(BSKY_SERVICE)
const needsEmailVerification = !isSelfHost && !currentAccount?.emailConfirmed
return {needsEmailVerification}
}
+24 -2
View File
@@ -4,10 +4,11 @@ import {useKeyboardController} from 'react-native-keyboard-controller'
import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native' import {useFocusEffect, useNavigation} from '@react-navigation/native'
import {NativeStackScreenProps} from '@react-navigation/native-stack' import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {CommonNavigatorParams} from '#/lib/routes/types' import {useEmail} from '#/lib/hooks/useEmail'
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {isWeb} from '#/platform/detection' import {isWeb} from '#/platform/detection'
import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileShadow} from '#/state/cache/profile-shadow'
import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo' import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo'
@@ -19,6 +20,8 @@ import {useSetMinimalShellMode} from '#/state/shell'
import {CenteredView} from '#/view/com/util/Views' import {CenteredView} from '#/view/com/util/Views'
import {MessagesList} from '#/screens/Messages/components/MessagesList' import {MessagesList} from '#/screens/Messages/components/MessagesList'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import {useDialogControl} from '#/components/Dialog'
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
import {MessagesListBlockedFooter} from '#/components/dms/MessagesListBlockedFooter' import {MessagesListBlockedFooter} from '#/components/dms/MessagesListBlockedFooter'
import {MessagesListHeader} from '#/components/dms/MessagesListHeader' import {MessagesListHeader} from '#/components/dms/MessagesListHeader'
import {Error} from '#/components/Error' import {Error} from '#/components/Error'
@@ -161,8 +164,12 @@ function InnerReady({
hasScrolled: boolean hasScrolled: boolean
setHasScrolled: React.Dispatch<React.SetStateAction<boolean>> setHasScrolled: React.Dispatch<React.SetStateAction<boolean>>
}) { }) {
const {_} = useLingui()
const convoState = useConvo() const convoState = useConvo()
const navigation = useNavigation<NavigationProp>()
const recipient = useProfileShadow(recipientUnshadowed) const recipient = useProfileShadow(recipientUnshadowed)
const verifyEmailControl = useDialogControl()
const {needsEmailVerification} = useEmail()
const moderation = React.useMemo(() => { const moderation = React.useMemo(() => {
return moderateProfile(recipient, moderationOpts) return moderateProfile(recipient, moderationOpts)
@@ -179,6 +186,12 @@ function InnerReady({
} }
}, [moderation]) }, [moderation])
React.useEffect(() => {
if (needsEmailVerification) {
verifyEmailControl.open()
}
}, [needsEmailVerification, verifyEmailControl])
return ( return (
<> <>
<MessagesListHeader <MessagesListHeader
@@ -201,6 +214,15 @@ function InnerReady({
} }
/> />
)} )}
<VerifyEmailDialog
reasonText={_(
msg`Before you may message another user, you must first verify your email.`,
)}
control={verifyEmailControl}
onCloseWithoutVerifying={() => {
navigation.navigate('Home')
}}
/>
</> </>
) )
} }
@@ -18,6 +18,7 @@ import Graphemer from 'graphemer'
import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants' import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
import {useHaptics} from '#/lib/haptics' import {useHaptics} from '#/lib/haptics'
import {useEmail} from '#/lib/hooks/useEmail'
import {isIOS} from '#/platform/detection' import {isIOS} from '#/platform/detection'
import { import {
useMessageDraft, useMessageDraft,
@@ -61,10 +62,15 @@ export function MessageInput({
const [message, setMessage] = React.useState(getDraft) const [message, setMessage] = React.useState(getDraft)
const inputRef = useAnimatedRef<TextInput>() const inputRef = useAnimatedRef<TextInput>()
const {needsEmailVerification} = useEmail()
useSaveMessageDraft(message) useSaveMessageDraft(message)
useExtractEmbedFromFacets(message, setEmbed) useExtractEmbedFromFacets(message, setEmbed)
const onSubmit = React.useCallback(() => { const onSubmit = React.useCallback(() => {
if (needsEmailVerification) {
return
}
if (!hasEmbed && message.trim() === '') { if (!hasEmbed && message.trim() === '') {
return return
} }
@@ -84,6 +90,7 @@ export function MessageInput({
inputRef.current?.focus() inputRef.current?.focus()
}, 100) }, 100)
}, [ }, [
needsEmailVerification,
hasEmbed, hasEmbed,
message, message,
clearDraft, clearDraft,
@@ -159,6 +166,7 @@ export function MessageInput({
ref={inputRef} ref={inputRef}
hitSlop={HITSLOP_10} hitSlop={HITSLOP_10}
animatedProps={animatedProps} animatedProps={animatedProps}
editable={!needsEmailVerification}
/> />
<Pressable <Pressable
accessibilityRole="button" accessibilityRole="button"
@@ -171,7 +179,8 @@ export function MessageInput({
a.justify_center, a.justify_center,
{height: 30, width: 30, backgroundColor: t.palette.primary_500}, {height: 30, width: 30, backgroundColor: t.palette.primary_500},
]} ]}
onPress={onSubmit}> onPress={onSubmit}
disabled={needsEmailVerification}>
<PaperPlane fill={t.palette.white} style={[a.relative, {left: 1}]} /> <PaperPlane fill={t.palette.white} style={[a.relative, {left: 1}]} />
</Pressable> </Pressable>
</View> </View>
@@ -0,0 +1,25 @@
import {useQuery} from '@tanstack/react-query'
interface ServiceConfig {
checkEmailConfirmed: boolean
}
export function useServiceConfigQuery() {
return useQuery({
queryKey: ['service-config'],
queryFn: async () => {
const res = await fetch(
'https://api.bsky.app/xrpc/app.bsky.unspecced.getConfig',
)
if (!res.ok) {
return {
checkEmailConfirmed: false,
}
}
const json = await res.json()
return json as ServiceConfig
},
staleTime: 5 * 60 * 1000,
})
}
+21
View File
@@ -58,6 +58,7 @@ import {EmbeddingDisabledError} from '#/lib/api/resolve'
import {until} from '#/lib/async/until' import {until} from '#/lib/async/until'
import {MAX_GRAPHEME_LENGTH} from '#/lib/constants' import {MAX_GRAPHEME_LENGTH} from '#/lib/constants'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {useEmail} from '#/lib/hooks/useEmail'
import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible' import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
@@ -110,6 +111,8 @@ import * as Toast from '#/view/com/util/Toast'
import {UserAvatar} from '#/view/com/util/UserAvatar' import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, native, useTheme} from '#/alf' import {atoms as a, native, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji' import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
@@ -297,6 +300,15 @@ export const ComposePost = ({
} }
}, [onPressCancel, closeAllDialogs, closeAllModals]) }, [onPressCancel, closeAllDialogs, closeAllModals])
const {needsEmailVerification} = useEmail()
const emailVerificationControl = useDialogControl()
useEffect(() => {
if (needsEmailVerification) {
emailVerificationControl.open()
}
}, [needsEmailVerification, emailVerificationControl])
const missingAltError = useMemo(() => { const missingAltError = useMemo(() => {
if (!requireAltTextEnabled) { if (!requireAltTextEnabled) {
return return
@@ -570,6 +582,15 @@ export const ComposePost = ({
const isWebFooterSticky = !isNative && thread.posts.length > 1 const isWebFooterSticky = !isNative && thread.posts.length > 1
return ( return (
<BottomSheetPortalProvider> <BottomSheetPortalProvider>
<VerifyEmailDialog
control={emailVerificationControl}
onCloseWithoutVerifying={() => {
onClose()
}}
reasonText={_(
msg`Before creating a post, you must first verify your email.`,
)}
/>
<KeyboardAvoidingView <KeyboardAvoidingView
testID="composePostView" testID="composePostView"
behavior={isIOS ? 'padding' : 'height'} behavior={isIOS ? 'padding' : 'height'}
+20 -2
View File
@@ -2,9 +2,11 @@ import React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {AtUri} from '@atproto/api' import {AtUri} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect, useNavigation} from '@react-navigation/native' import {useFocusEffect, useNavigation} from '@react-navigation/native'
import {useEmail} from '#/lib/hooks/useEmail'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
@@ -16,15 +18,20 @@ import {MyLists} from '#/view/com/lists/MyLists'
import {Button} from '#/view/com/util/forms/Button' import {Button} from '#/view/com/util/forms/Button'
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader' import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
import {Text} from '#/view/com/util/text/Text' import {Text} from '#/view/com/util/text/Text'
import {useDialogControl} from '#/components/Dialog'
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
import * as Layout from '#/components/Layout' import * as Layout from '#/components/Layout'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'>
export function ListsScreen({}: Props) { export function ListsScreen({}: Props) {
const {_} = useLingui()
const pal = usePalette('default') const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {openModal} = useModalControls() const {openModal} = useModalControls()
const {needsEmailVerification} = useEmail()
const control = useDialogControl()
useFocusEffect( useFocusEffect(
React.useCallback(() => { React.useCallback(() => {
@@ -33,6 +40,11 @@ export function ListsScreen({}: Props) {
) )
const onPressNewList = React.useCallback(() => { const onPressNewList = React.useCallback(() => {
if (needsEmailVerification) {
control.open()
return
}
openModal({ openModal({
name: 'create-or-edit-list', name: 'create-or-edit-list',
purpose: 'app.bsky.graph.defs#curatelist', purpose: 'app.bsky.graph.defs#curatelist',
@@ -46,7 +58,7 @@ export function ListsScreen({}: Props) {
} catch {} } catch {}
}, },
}) })
}, [openModal, navigation]) }, [needsEmailVerification, control, openModal, navigation])
return ( return (
<Layout.Screen testID="listsScreen"> <Layout.Screen testID="listsScreen">
@@ -87,6 +99,12 @@ export function ListsScreen({}: Props) {
</View> </View>
</SimpleViewHeader> </SimpleViewHeader>
<MyLists filter="curate" style={s.flexGrow1} /> <MyLists filter="curate" style={s.flexGrow1} />
<VerifyEmailDialog
reasonText={_(
msg`Before creating a list, you must first verify your email.`,
)}
control={control}
/>
</Layout.Screen> </Layout.Screen>
) )
} }
+20 -2
View File
@@ -2,9 +2,11 @@ import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {AtUri} from '@atproto/api' import {AtUri} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect, useNavigation} from '@react-navigation/native' import {useFocusEffect, useNavigation} from '@react-navigation/native'
import {useEmail} from '#/lib/hooks/useEmail'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
@@ -16,15 +18,20 @@ import {MyLists} from '#/view/com/lists/MyLists'
import {Button} from '#/view/com/util/forms/Button' import {Button} from '#/view/com/util/forms/Button'
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader' import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
import {Text} from '#/view/com/util/text/Text' import {Text} from '#/view/com/util/text/Text'
import {useDialogControl} from '#/components/Dialog'
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
import * as Layout from '#/components/Layout' import * as Layout from '#/components/Layout'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ModerationModlists'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'ModerationModlists'>
export function ModerationModlistsScreen({}: Props) { export function ModerationModlistsScreen({}: Props) {
const {_} = useLingui()
const pal = usePalette('default') const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {openModal} = useModalControls() const {openModal} = useModalControls()
const {needsEmailVerification} = useEmail()
const control = useDialogControl()
useFocusEffect( useFocusEffect(
React.useCallback(() => { React.useCallback(() => {
@@ -33,6 +40,11 @@ export function ModerationModlistsScreen({}: Props) {
) )
const onPressNewList = React.useCallback(() => { const onPressNewList = React.useCallback(() => {
if (needsEmailVerification) {
control.open()
return
}
openModal({ openModal({
name: 'create-or-edit-list', name: 'create-or-edit-list',
purpose: 'app.bsky.graph.defs#modlist', purpose: 'app.bsky.graph.defs#modlist',
@@ -46,7 +58,7 @@ export function ModerationModlistsScreen({}: Props) {
} catch {} } catch {}
}, },
}) })
}, [openModal, navigation]) }, [needsEmailVerification, control, openModal, navigation])
return ( return (
<Layout.Screen testID="moderationModlistsScreen"> <Layout.Screen testID="moderationModlistsScreen">
@@ -83,6 +95,12 @@ export function ModerationModlistsScreen({}: Props) {
</View> </View>
</SimpleViewHeader> </SimpleViewHeader>
<MyLists filter="mod" style={s.flexGrow1} /> <MyLists filter="mod" style={s.flexGrow1} />
<VerifyEmailDialog
reasonText={_(
msg`Before creating a list, you must first verify your email.`,
)}
control={control}
/>
</Layout.Screen> </Layout.Screen>
) )
} }