1
- import { AgreeToTermsPolicy , SignInIdentifier } from '@logto/schemas' ;
1
+ import { AgreeToTermsPolicy , SignInIdentifier , type RequestErrorBody } from '@logto/schemas' ;
2
+ import { condString } from '@silverhand/essentials' ;
2
3
import { useCallback , useEffect , useState } from 'react' ;
3
4
import { useParams } from 'react-router-dom' ;
4
5
5
6
import {
6
7
identifyAndSubmitInteraction ,
7
8
signInWithVerifiedIdentifier ,
8
- verifyOneTimeToken ,
9
+ registerWithOneTimeToken ,
9
10
} from '@/apis/experience' ;
10
11
import LoadingLayer from '@/components/LoadingLayer' ;
11
12
import useApi from '@/hooks/use-api' ;
12
13
import useErrorHandler from '@/hooks/use-error-handler' ;
13
- import useFallbackRoute from '@/hooks/use-fallback-route' ;
14
14
import useGlobalRedirectTo from '@/hooks/use-global-redirect-to' ;
15
15
import useLoginHint from '@/hooks/use-login-hint' ;
16
16
import usePreSignInErrorHandler from '@/hooks/use-pre-sign-in-error-handler' ;
17
17
import useTerms from '@/hooks/use-terms' ;
18
18
19
19
import ErrorPage from '../ErrorPage' ;
20
- import SwitchAccount from '../SwitchAccount' ;
21
20
22
21
const OneTimeToken = ( ) => {
23
22
const { token } = useParams ( ) ;
24
- const fallback = useFallbackRoute ( ) ;
25
23
const email = useLoginHint ( ) ;
26
- const [ mismatchedAccount , setMismatchedAccount ] = useState < string > ( ) ;
27
- const [ oneTimeTokenError , setOneTimeTokenError ] = useState < unknown > ( ) ;
24
+ const [ oneTimeTokenError , setOneTimeTokenError ] = useState < RequestErrorBody | boolean > ( ) ;
28
25
29
- const asyncRegisterWithOneTimeToken = useApi ( identifyAndSubmitInteraction ) ;
26
+ const asyncIdentifyUserAndSubmit = useApi ( identifyAndSubmitInteraction ) ;
30
27
const asyncSignInWithVerifiedIdentifier = useApi ( signInWithVerifiedIdentifier ) ;
31
- const asyncVerifyOneTimeToken = useApi ( verifyOneTimeToken ) ;
28
+ const asyncRegisterWithOneTimeToken = useApi ( registerWithOneTimeToken ) ;
32
29
33
30
const { termsValidation, agreeToTermsPolicy } = useTerms ( ) ;
34
31
const handleError = useErrorHandler ( ) ;
35
32
const redirectTo = useGlobalRedirectTo ( ) ;
36
33
const preSignInErrorHandler = usePreSignInErrorHandler ( ) ;
37
34
35
+ /**
36
+ * Update interaction event to `SignIn`, and then identify user and submit.
37
+ */
38
38
const signInWithOneTimeToken = useCallback (
39
39
async ( verificationId : string ) => {
40
40
const [ error , result ] = await asyncSignInWithVerifiedIdentifier ( verificationId ) ;
@@ -51,9 +51,13 @@ const OneTimeToken = () => {
51
51
[ preSignInErrorHandler , asyncSignInWithVerifiedIdentifier , handleError , redirectTo ]
52
52
) ;
53
53
54
- const registerWithOneTimeToken = useCallback (
54
+ /**
55
+ * Always try to submit the one-time token interaction with `Register` event first.
56
+ * If the email already exists, call the `signInWithOneTimeToken` function instead.
57
+ */
58
+ const submit = useCallback (
55
59
async ( verificationId : string ) => {
56
- const [ error , result ] = await asyncRegisterWithOneTimeToken ( { verificationId } ) ;
60
+ const [ error , result ] = await asyncIdentifyUserAndSubmit ( { verificationId } ) ;
57
61
58
62
if ( error ) {
59
63
await handleError ( error , {
@@ -68,70 +72,61 @@ const OneTimeToken = () => {
68
72
await redirectTo ( result . redirectTo ) ;
69
73
}
70
74
} ,
71
- [ asyncRegisterWithOneTimeToken , handleError , redirectTo , signInWithOneTimeToken ]
75
+ [ asyncIdentifyUserAndSubmit , handleError , redirectTo , signInWithOneTimeToken ]
72
76
) ;
73
77
74
78
useEffect ( ( ) => {
75
79
( async ( ) => {
76
- if ( token && email ) {
77
- /**
78
- * Check if the user has agreed to the terms and privacy policy before navigating to the 3rd-party social sign-in page
79
- * when the policy is set to `Manual`
80
- */
81
- if ( agreeToTermsPolicy === AgreeToTermsPolicy . Manual && ! ( await termsValidation ( ) ) ) {
82
- return ;
83
- }
84
- const [ error , result ] = await asyncVerifyOneTimeToken ( {
85
- token ,
86
- identifier : { type : SignInIdentifier . Email , value : email } ,
87
- } ) ;
80
+ if ( ! token || ! email ) {
81
+ setOneTimeTokenError ( true ) ;
82
+ return ;
83
+ }
84
+
85
+ /**
86
+ * Check if the user has agreed to the terms and privacy policy before navigating to the 3rd-party social sign-in page
87
+ * when the policy is set to `Manual`
88
+ */
89
+ if ( agreeToTermsPolicy === AgreeToTermsPolicy . Manual && ! ( await termsValidation ( ) ) ) {
90
+ return ;
91
+ }
88
92
89
- if ( error ) {
90
- await handleError ( error , {
91
- 'one_time_token.email_mismatch' : ( ) => {
92
- setMismatchedAccount ( email ) ;
93
- } ,
94
- 'one_time_token.token_expired' : ( ) => {
95
- setOneTimeTokenError ( error ) ;
96
- } ,
97
- 'one_time_token.token_consumed' : ( ) => {
98
- setOneTimeTokenError ( error ) ;
99
- } ,
100
- 'one_time_token.token_revoked' : ( ) => {
101
- setOneTimeTokenError ( error ) ;
102
- } ,
103
- 'one_time_token.token_not_found' : ( ) => {
104
- setOneTimeTokenError ( error ) ;
105
- } ,
106
- } ) ;
107
- return ;
108
- }
109
-
110
- if ( ! result ?. verificationId ) {
111
- return ;
112
- }
113
- await registerWithOneTimeToken ( result . verificationId ) ;
93
+ const [ error , result ] = await asyncRegisterWithOneTimeToken ( {
94
+ token,
95
+ identifier : { type : SignInIdentifier . Email , value : email } ,
96
+ } ) ;
97
+
98
+ if ( error ) {
99
+ await handleError ( error , {
100
+ global : ( error : RequestErrorBody ) => {
101
+ setOneTimeTokenError ( error ) ;
102
+ } ,
103
+ } ) ;
104
+ return ;
114
105
}
115
106
116
- window . location . replace ( '/' + fallback ) ;
107
+ if ( ! result ?. verificationId ) {
108
+ return ;
109
+ }
110
+ await submit ( result . verificationId ) ;
117
111
} ) ( ) ;
118
112
} , [
119
113
agreeToTermsPolicy ,
120
114
email ,
121
- fallback ,
122
115
token ,
123
- asyncVerifyOneTimeToken ,
116
+ asyncRegisterWithOneTimeToken ,
124
117
handleError ,
125
- registerWithOneTimeToken ,
126
118
termsValidation ,
119
+ submit ,
127
120
] ) ;
128
121
129
- if ( mismatchedAccount ) {
130
- return < SwitchAccount account = { mismatchedAccount } /> ;
131
- }
132
-
133
122
if ( oneTimeTokenError ) {
134
- return < ErrorPage title = "error.invalid_link" message = "error.invalid_link_description" /> ;
123
+ return (
124
+ < ErrorPage
125
+ title = "error.invalid_link"
126
+ message = "error.invalid_link_description"
127
+ rawMessage = { condString ( typeof oneTimeTokenError !== 'boolean' && oneTimeTokenError . message ) }
128
+ />
129
+ ) ;
135
130
}
136
131
137
132
return < LoadingLayer /> ;
0 commit comments