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