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 {ThemeProvider as Alf} from '#/alf'
|
||||||
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
|
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
|
||||||
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
|
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
|
||||||
|
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
|
||||||
import {Provider as PortalProvider} from '#/components/Portal'
|
import {Provider as PortalProvider} from '#/components/Portal'
|
||||||
import {Splash} from '#/Splash'
|
import {Splash} from '#/Splash'
|
||||||
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
||||||
|
@ -105,52 +106,50 @@ function InnerApp() {
|
||||||
}, [_])
|
}, [_])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
|
<Alf theme={theme}>
|
||||||
<Alf theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<ThemeProvider theme={theme}>
|
<Splash isReady={isReady && hasCheckedReferrer}>
|
||||||
<Splash isReady={isReady && hasCheckedReferrer}>
|
<ActiveVideoProvider>
|
||||||
<ActiveVideoProvider>
|
<RootSiblingParent>
|
||||||
<RootSiblingParent>
|
<React.Fragment
|
||||||
<React.Fragment
|
// Resets the entire tree below when it changes:
|
||||||
// Resets the entire tree below when it changes:
|
key={currentAccount?.did}>
|
||||||
key={currentAccount?.did}>
|
<QueryProvider currentDid={currentAccount?.did}>
|
||||||
<QueryProvider currentDid={currentAccount?.did}>
|
<StatsigProvider>
|
||||||
<StatsigProvider>
|
<MessagesProvider>
|
||||||
<MessagesProvider>
|
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
<LabelDefsProvider>
|
||||||
<LabelDefsProvider>
|
<ModerationOptsProvider>
|
||||||
<ModerationOptsProvider>
|
<LoggedOutViewProvider>
|
||||||
<LoggedOutViewProvider>
|
<SelectedFeedProvider>
|
||||||
<SelectedFeedProvider>
|
<HiddenRepliesProvider>
|
||||||
<HiddenRepliesProvider>
|
<UnreadNotifsProvider>
|
||||||
<UnreadNotifsProvider>
|
<BackgroundNotificationPreferencesProvider>
|
||||||
<BackgroundNotificationPreferencesProvider>
|
<MutedThreadsProvider>
|
||||||
<MutedThreadsProvider>
|
<ProgressGuideProvider>
|
||||||
<ProgressGuideProvider>
|
<GestureHandlerRootView
|
||||||
<GestureHandlerRootView
|
style={s.h100pct}>
|
||||||
style={s.h100pct}>
|
<TestCtrls />
|
||||||
<TestCtrls />
|
<Shell />
|
||||||
<Shell />
|
</GestureHandlerRootView>
|
||||||
</GestureHandlerRootView>
|
</ProgressGuideProvider>
|
||||||
</ProgressGuideProvider>
|
</MutedThreadsProvider>
|
||||||
</MutedThreadsProvider>
|
</BackgroundNotificationPreferencesProvider>
|
||||||
</BackgroundNotificationPreferencesProvider>
|
</UnreadNotifsProvider>
|
||||||
</UnreadNotifsProvider>
|
</HiddenRepliesProvider>
|
||||||
</HiddenRepliesProvider>
|
</SelectedFeedProvider>
|
||||||
</SelectedFeedProvider>
|
</LoggedOutViewProvider>
|
||||||
</LoggedOutViewProvider>
|
</ModerationOptsProvider>
|
||||||
</ModerationOptsProvider>
|
</LabelDefsProvider>
|
||||||
</LabelDefsProvider>
|
</MessagesProvider>
|
||||||
</MessagesProvider>
|
</StatsigProvider>
|
||||||
</StatsigProvider>
|
</QueryProvider>
|
||||||
</QueryProvider>
|
</React.Fragment>
|
||||||
</React.Fragment>
|
</RootSiblingParent>
|
||||||
</RootSiblingParent>
|
</ActiveVideoProvider>
|
||||||
</ActiveVideoProvider>
|
</Splash>
|
||||||
</Splash>
|
</ThemeProvider>
|
||||||
</ThemeProvider>
|
</Alf>
|
||||||
</Alf>
|
|
||||||
</SafeAreaProvider>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +183,12 @@ function App() {
|
||||||
<LightboxStateProvider>
|
<LightboxStateProvider>
|
||||||
<PortalProvider>
|
<PortalProvider>
|
||||||
<StarterPackProvider>
|
<StarterPackProvider>
|
||||||
<InnerApp />
|
<SafeAreaProvider
|
||||||
|
initialMetrics={initialWindowMetrics}>
|
||||||
|
<IntentDialogProvider>
|
||||||
|
<InnerApp />
|
||||||
|
</IntentDialogProvider>
|
||||||
|
</SafeAreaProvider>
|
||||||
</StarterPackProvider>
|
</StarterPackProvider>
|
||||||
</PortalProvider>
|
</PortalProvider>
|
||||||
</LightboxStateProvider>
|
</LightboxStateProvider>
|
||||||
|
|
|
@ -47,6 +47,7 @@ import {Shell} from '#/view/shell/index'
|
||||||
import {ThemeProvider as Alf} from '#/alf'
|
import {ThemeProvider as Alf} from '#/alf'
|
||||||
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
|
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
|
||||||
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
|
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
|
||||||
|
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
|
||||||
import {Provider as PortalProvider} from '#/components/Portal'
|
import {Provider as PortalProvider} from '#/components/Portal'
|
||||||
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
||||||
|
|
||||||
|
@ -162,7 +163,9 @@ function App() {
|
||||||
<LightboxStateProvider>
|
<LightboxStateProvider>
|
||||||
<PortalProvider>
|
<PortalProvider>
|
||||||
<StarterPackProvider>
|
<StarterPackProvider>
|
||||||
<InnerApp />
|
<IntentDialogProvider>
|
||||||
|
<InnerApp />
|
||||||
|
</IntentDialogProvider>
|
||||||
</StarterPackProvider>
|
</StarterPackProvider>
|
||||||
</PortalProvider>
|
</PortalProvider>
|
||||||
</LightboxStateProvider>
|
</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 {useSession} from 'state/session'
|
||||||
import {useComposerControls} from 'state/shell'
|
import {useComposerControls} from 'state/shell'
|
||||||
import {useCloseAllActiveElements} from 'state/util'
|
import {useCloseAllActiveElements} from 'state/util'
|
||||||
|
import {useIntentDialogs} from '#/components/intents/IntentDialogs'
|
||||||
import {Referrer} from '../../../modules/expo-bluesky-swiss-army'
|
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+)?$/
|
const VALID_IMAGE_REGEX = /^[\w.:\-_/]+\|\d+(\.\d+)?\|\d+(\.\d+)?$/
|
||||||
|
|
||||||
export function useIntentHandler() {
|
export function useIntentHandler() {
|
||||||
const incomingUrl = Linking.useURL()
|
const incomingUrl = Linking.useURL()
|
||||||
const composeIntent = useComposeIntent()
|
const composeIntent = useComposeIntent()
|
||||||
|
const verifyEmailIntent = useVerifyEmailIntent()
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const handleIncomingURL = (url: string) => {
|
const handleIncomingURL = (url: string) => {
|
||||||
|
@ -51,12 +53,22 @@ export function useIntentHandler() {
|
||||||
text: params.get('text'),
|
text: params.get('text'),
|
||||||
imageUrisStr: params.get('imageUris'),
|
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)
|
if (incomingUrl) handleIncomingURL(incomingUrl)
|
||||||
}, [incomingUrl, composeIntent])
|
}, [incomingUrl, composeIntent, verifyEmailIntent])
|
||||||
}
|
}
|
||||||
|
|
||||||
function useComposeIntent() {
|
function useComposeIntent() {
|
||||||
|
@ -103,3 +115,21 @@ function useComposeIntent() {
|
||||||
[hasSession, closeAllActiveElements, openComposer],
|
[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