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,15 +51,20 @@ 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 , {
60
64
'user.email_already_in_use' : async ( ) => {
61
65
await signInWithOneTimeToken ( verificationId ) ;
62
66
} ,
67
+ ...preSignInErrorHandler ,
63
68
} ) ;
64
69
return ;
65
70
}
@@ -68,70 +73,67 @@ const OneTimeToken = () => {
68
73
await redirectTo ( result . redirectTo ) ;
69
74
}
70
75
} ,
71
- [ asyncRegisterWithOneTimeToken , handleError , redirectTo , signInWithOneTimeToken ]
76
+ [
77
+ preSignInErrorHandler ,
78
+ asyncIdentifyUserAndSubmit ,
79
+ handleError ,
80
+ redirectTo ,
81
+ signInWithOneTimeToken ,
82
+ ]
72
83
) ;
73
84
74
85
useEffect ( ( ) => {
75
86
( 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
- } ) ;
87
+ if ( ! token || ! email ) {
88
+ setOneTimeTokenError ( true ) ;
89
+ return ;
90
+ }
91
+
92
+ /**
93
+ * Check if the user has agreed to the terms and privacy policy before navigating to the 3rd-party social sign-in page
94
+ * when the policy is set to `Manual`
95
+ */
96
+ if ( agreeToTermsPolicy === AgreeToTermsPolicy . Manual && ! ( await termsValidation ( ) ) ) {
97
+ return ;
98
+ }
88
99
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 ) ;
100
+ const [ error , result ] = await asyncRegisterWithOneTimeToken ( {
101
+ token,
102
+ identifier : { type : SignInIdentifier . Email , value : email } ,
103
+ } ) ;
104
+
105
+ if ( error ) {
106
+ await handleError ( error , {
107
+ global : ( error : RequestErrorBody ) => {
108
+ setOneTimeTokenError ( error ) ;
109
+ } ,
110
+ } ) ;
111
+ return ;
114
112
}
115
113
116
- window . location . replace ( '/' + fallback ) ;
114
+ if ( ! result ?. verificationId ) {
115
+ return ;
116
+ }
117
+ await submit ( result . verificationId ) ;
117
118
} ) ( ) ;
118
119
} , [
119
120
agreeToTermsPolicy ,
120
121
email ,
121
- fallback ,
122
122
token ,
123
- asyncVerifyOneTimeToken ,
123
+ asyncRegisterWithOneTimeToken ,
124
124
handleError ,
125
- registerWithOneTimeToken ,
126
125
termsValidation ,
126
+ submit ,
127
127
] ) ;
128
128
129
- if ( mismatchedAccount ) {
130
- return < SwitchAccount account = { mismatchedAccount } /> ;
131
- }
132
-
133
129
if ( oneTimeTokenError ) {
134
- return < ErrorPage title = "error.invalid_link" message = "error.invalid_link_description" /> ;
130
+ return (
131
+ < ErrorPage
132
+ title = "error.invalid_link"
133
+ message = "error.invalid_link_description"
134
+ rawMessage = { condString ( typeof oneTimeTokenError !== 'boolean' && oneTimeTokenError . message ) }
135
+ />
136
+ ) ;
135
137
}
136
138
137
139
return < LoadingLayer /> ;
0 commit comments