Add intent for verifying email (#5120)
parent
45a719b256
commit
2842f661db
|
@ -58,6 +58,7 @@ import {Shell} from '#/view/shell'
|
|||
import {ThemeProvider as Alf} from '#/alf'
|
||||
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
|
||||
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
|
||||
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
|
||||
import {Provider as PortalProvider} from '#/components/Portal'
|
||||
import {Splash} from '#/Splash'
|
||||
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
||||
|
@ -105,52 +106,50 @@ function InnerApp() {
|
|||
}, [_])
|
||||
|
||||
return (
|
||||
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
|
||||
<Alf theme={theme}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<Splash isReady={isReady && hasCheckedReferrer}>
|
||||
<ActiveVideoProvider>
|
||||
<RootSiblingParent>
|
||||
<React.Fragment
|
||||
// Resets the entire tree below when it changes:
|
||||
key={currentAccount?.did}>
|
||||
<QueryProvider currentDid={currentAccount?.did}>
|
||||
<StatsigProvider>
|
||||
<MessagesProvider>
|
||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||
<LabelDefsProvider>
|
||||
<ModerationOptsProvider>
|
||||
<LoggedOutViewProvider>
|
||||
<SelectedFeedProvider>
|
||||
<HiddenRepliesProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<BackgroundNotificationPreferencesProvider>
|
||||
<MutedThreadsProvider>
|
||||
<ProgressGuideProvider>
|
||||
<GestureHandlerRootView
|
||||
style={s.h100pct}>
|
||||
<TestCtrls />
|
||||
<Shell />
|
||||
</GestureHandlerRootView>
|
||||
</ProgressGuideProvider>
|
||||
</MutedThreadsProvider>
|
||||
</BackgroundNotificationPreferencesProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</HiddenRepliesProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</ModerationOptsProvider>
|
||||
</LabelDefsProvider>
|
||||
</MessagesProvider>
|
||||
</StatsigProvider>
|
||||
</QueryProvider>
|
||||
</React.Fragment>
|
||||
</RootSiblingParent>
|
||||
</ActiveVideoProvider>
|
||||
</Splash>
|
||||
</ThemeProvider>
|
||||
</Alf>
|
||||
</SafeAreaProvider>
|
||||
<Alf theme={theme}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<Splash isReady={isReady && hasCheckedReferrer}>
|
||||
<ActiveVideoProvider>
|
||||
<RootSiblingParent>
|
||||
<React.Fragment
|
||||
// Resets the entire tree below when it changes:
|
||||
key={currentAccount?.did}>
|
||||
<QueryProvider currentDid={currentAccount?.did}>
|
||||
<StatsigProvider>
|
||||
<MessagesProvider>
|
||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||
<LabelDefsProvider>
|
||||
<ModerationOptsProvider>
|
||||
<LoggedOutViewProvider>
|
||||
<SelectedFeedProvider>
|
||||
<HiddenRepliesProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<BackgroundNotificationPreferencesProvider>
|
||||
<MutedThreadsProvider>
|
||||
<ProgressGuideProvider>
|
||||
<GestureHandlerRootView
|
||||
style={s.h100pct}>
|
||||
<TestCtrls />
|
||||
<Shell />
|
||||
</GestureHandlerRootView>
|
||||
</ProgressGuideProvider>
|
||||
</MutedThreadsProvider>
|
||||
</BackgroundNotificationPreferencesProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</HiddenRepliesProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</ModerationOptsProvider>
|
||||
</LabelDefsProvider>
|
||||
</MessagesProvider>
|
||||
</StatsigProvider>
|
||||
</QueryProvider>
|
||||
</React.Fragment>
|
||||
</RootSiblingParent>
|
||||
</ActiveVideoProvider>
|
||||
</Splash>
|
||||
</ThemeProvider>
|
||||
</Alf>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -184,7 +183,12 @@ function App() {
|
|||
<LightboxStateProvider>
|
||||
<PortalProvider>
|
||||
<StarterPackProvider>
|
||||
<InnerApp />
|
||||
<SafeAreaProvider
|
||||
initialMetrics={initialWindowMetrics}>
|
||||
<IntentDialogProvider>
|
||||
<InnerApp />
|
||||
</IntentDialogProvider>
|
||||
</SafeAreaProvider>
|
||||
</StarterPackProvider>
|
||||
</PortalProvider>
|
||||
</LightboxStateProvider>
|
||||
|
|
|
@ -47,6 +47,7 @@ import {Shell} from '#/view/shell/index'
|
|||
import {ThemeProvider as Alf} from '#/alf'
|
||||
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
|
||||
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
|
||||
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
|
||||
import {Provider as PortalProvider} from '#/components/Portal'
|
||||
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
||||
|
||||
|
@ -162,7 +163,9 @@ function App() {
|
|||
<LightboxStateProvider>
|
||||
<PortalProvider>
|
||||
<StarterPackProvider>
|
||||
<InnerApp />
|
||||
<IntentDialogProvider>
|
||||
<InnerApp />
|
||||
</IntentDialogProvider>
|
||||
</StarterPackProvider>
|
||||
</PortalProvider>
|
||||
</LightboxStateProvider>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import React from 'react'
|
||||
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {DialogControlProps} from '#/components/Dialog'
|
||||
import {VerifyEmailIntentDialog} from '#/components/intents/VerifyEmailIntentDialog'
|
||||
|
||||
interface Context {
|
||||
verifyEmailDialogControl: DialogControlProps
|
||||
verifyEmailState: {code: string} | undefined
|
||||
setVerifyEmailState: (state: {code: string} | undefined) => void
|
||||
}
|
||||
|
||||
const Context = React.createContext({} as Context)
|
||||
export const useIntentDialogs = () => React.useContext(Context)
|
||||
|
||||
export function Provider({children}: {children: React.ReactNode}) {
|
||||
const verifyEmailDialogControl = Dialog.useDialogControl()
|
||||
const [verifyEmailState, setVerifyEmailState] = React.useState<
|
||||
{code: string} | undefined
|
||||
>()
|
||||
|
||||
const value = React.useMemo(
|
||||
() => ({
|
||||
verifyEmailDialogControl,
|
||||
verifyEmailState,
|
||||
setVerifyEmailState,
|
||||
}),
|
||||
[verifyEmailDialogControl, verifyEmailState, setVerifyEmailState],
|
||||
)
|
||||
|
||||
return (
|
||||
<Context.Provider value={value}>
|
||||
{children}
|
||||
<VerifyEmailIntentDialog />
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useAgent, useSession} from 'state/session'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {DialogControlProps} from '#/components/Dialog'
|
||||
import {useIntentDialogs} from '#/components/intents/IntentDialogs'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function VerifyEmailIntentDialog() {
|
||||
const {verifyEmailDialogControl: control} = useIntentDialogs()
|
||||
|
||||
return (
|
||||
<Dialog.Outer control={control}>
|
||||
<Dialog.Handle />
|
||||
<Inner control={control} />
|
||||
</Dialog.Outer>
|
||||
)
|
||||
}
|
||||
|
||||
function Inner({control}: {control: DialogControlProps}) {
|
||||
const {_} = useLingui()
|
||||
const {verifyEmailState: state} = useIntentDialogs()
|
||||
const [status, setStatus] = React.useState<
|
||||
'loading' | 'success' | 'failure' | 'resent'
|
||||
>('loading')
|
||||
const [sending, setSending] = React.useState(false)
|
||||
const agent = useAgent()
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
React.useEffect(() => {
|
||||
;(async () => {
|
||||
if (!state?.code) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await agent.com.atproto.server.confirmEmail({
|
||||
email: (currentAccount?.email || '').trim(),
|
||||
token: state.code.trim(),
|
||||
})
|
||||
setStatus('success')
|
||||
} catch (e) {
|
||||
setStatus('failure')
|
||||
}
|
||||
})()
|
||||
}, [agent.com.atproto.server, currentAccount?.email, state?.code])
|
||||
|
||||
const onPressResendEmail = async () => {
|
||||
setSending(true)
|
||||
await agent.com.atproto.server.requestEmailConfirmation()
|
||||
setSending(false)
|
||||
setStatus('resent')
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog.ScrollableInner label={_(msg`Verify email dialog`)}>
|
||||
<Dialog.Close />
|
||||
<View style={[a.gap_xl]}>
|
||||
{status === 'loading' ? (
|
||||
<View style={[a.py_2xl, a.align_center, a.justify_center]}>
|
||||
<Loader size="xl" />
|
||||
</View>
|
||||
) : status === 'success' ? (
|
||||
<>
|
||||
<Text style={[a.font_bold, a.text_2xl]}>
|
||||
<Trans>Email Verified</Trans>
|
||||
</Text>
|
||||
<Text style={[a.text_md, a.leading_tight]}>
|
||||
<Trans>
|
||||
Thanks, you have successfully verified your email address.
|
||||
</Trans>
|
||||
</Text>
|
||||
</>
|
||||
) : status === 'failure' ? (
|
||||
<>
|
||||
<Text style={[a.font_bold, a.text_2xl]}>
|
||||
<Trans>Invalid Verification Code</Trans>
|
||||
</Text>
|
||||
<Text style={[a.text_md, a.leading_tight]}>
|
||||
<Trans>
|
||||
The verification code you have provided is invalid. Please make
|
||||
sure that you have used the correct verification link or request
|
||||
a new one.
|
||||
</Trans>
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text style={[a.font_bold, a.text_2xl]}>
|
||||
<Trans>Email Resent</Trans>
|
||||
</Text>
|
||||
<Text style={[a.text_md, a.leading_tight]}>
|
||||
<Trans>
|
||||
We have sent another verification email to{' '}
|
||||
<Text style={[a.text_md, a.font_bold]}>
|
||||
{currentAccount?.email}
|
||||
</Text>
|
||||
.
|
||||
</Trans>
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
{status !== 'loading' ? (
|
||||
<View style={[a.w_full, a.flex_row, a.gap_sm, {marginLeft: 'auto'}]}>
|
||||
<Button
|
||||
label={_(msg`Close`)}
|
||||
onPress={() => control.close()}
|
||||
variant="solid"
|
||||
color={status === 'failure' ? 'secondary' : 'primary'}
|
||||
size="medium"
|
||||
style={{marginLeft: 'auto'}}>
|
||||
<ButtonText>
|
||||
<Trans>Close</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
{status === 'failure' ? (
|
||||
<Button
|
||||
label={_(msg`Resend Verification Email`)}
|
||||
onPress={onPressResendEmail}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="medium"
|
||||
disabled={sending}>
|
||||
<ButtonText>
|
||||
<Trans>Resend Email</Trans>
|
||||
</ButtonText>
|
||||
{sending ? <Loader size="sm" style={{color: 'white'}} /> : null}
|
||||
</Button>
|
||||
) : null}
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
</Dialog.ScrollableInner>
|
||||
)
|
||||
}
|
|
@ -6,15 +6,17 @@ import {isNative} from 'platform/detection'
|
|||
import {useSession} from 'state/session'
|
||||
import {useComposerControls} from 'state/shell'
|
||||
import {useCloseAllActiveElements} from 'state/util'
|
||||
import {useIntentDialogs} from '#/components/intents/IntentDialogs'
|
||||
import {Referrer} from '../../../modules/expo-bluesky-swiss-army'
|
||||
|
||||
type IntentType = 'compose'
|
||||
type IntentType = 'compose' | 'verify-email'
|
||||
|
||||
const VALID_IMAGE_REGEX = /^[\w.:\-_/]+\|\d+(\.\d+)?\|\d+(\.\d+)?$/
|
||||
|
||||
export function useIntentHandler() {
|
||||
const incomingUrl = Linking.useURL()
|
||||
const composeIntent = useComposeIntent()
|
||||
const verifyEmailIntent = useVerifyEmailIntent()
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleIncomingURL = (url: string) => {
|
||||
|
@ -51,12 +53,22 @@ export function useIntentHandler() {
|
|||
text: params.get('text'),
|
||||
imageUrisStr: params.get('imageUris'),
|
||||
})
|
||||
return
|
||||
}
|
||||
case 'verify-email': {
|
||||
const code = params.get('code')
|
||||
if (!code) return
|
||||
verifyEmailIntent(code)
|
||||
return
|
||||
}
|
||||
default: {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (incomingUrl) handleIncomingURL(incomingUrl)
|
||||
}, [incomingUrl, composeIntent])
|
||||
}, [incomingUrl, composeIntent, verifyEmailIntent])
|
||||
}
|
||||
|
||||
function useComposeIntent() {
|
||||
|
@ -103,3 +115,21 @@ function useComposeIntent() {
|
|||
[hasSession, closeAllActiveElements, openComposer],
|
||||
)
|
||||
}
|
||||
|
||||
function useVerifyEmailIntent() {
|
||||
const closeAllActiveElements = useCloseAllActiveElements()
|
||||
const {verifyEmailDialogControl: control, setVerifyEmailState: setState} =
|
||||
useIntentDialogs()
|
||||
return React.useCallback(
|
||||
(code: string) => {
|
||||
closeAllActiveElements()
|
||||
setState({
|
||||
code,
|
||||
})
|
||||
setTimeout(() => {
|
||||
control.open()
|
||||
}, 1000)
|
||||
},
|
||||
[closeAllActiveElements, control, setState],
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue