@@ -2,9 +2,16 @@ import type { WrapperProps } from '@docusaurus/types';
2
2
import useDocusaurusContext from '@docusaurus/useDocusaurusContext' ;
3
3
import type LayoutType from '@theme/Layout' ;
4
4
import Layout from '@theme-original/Layout' ;
5
- import { type ReactNode , useEffect } from 'react' ;
5
+ import { type ReactNode , useCallback , useEffect } from 'react' ;
6
6
7
- import { useDebugLogger , useApiBaseUrl , useGoogleOneTapConfig , useAuthStatus } from './hooks' ;
7
+ import {
8
+ useDebugLogger ,
9
+ useApiBaseUrl ,
10
+ useGoogleOneTapConfig ,
11
+ useAuthStatus ,
12
+ useGoogleOneTapVerify ,
13
+ } from './hooks' ;
14
+ import type { GoogleOneTapCredentialResponse , GoogleOneTapVerifyResponse } from './types' ;
8
15
9
16
type GoogleCredentialResponse = {
10
17
credential : string ;
@@ -16,9 +23,71 @@ export default function LayoutWrapper(props: Props): ReactNode {
16
23
// Hooks must be called at the top level, outside of try-catch
17
24
const { siteConfig } = useDocusaurusContext ( ) ;
18
25
const debugLogger = useDebugLogger ( siteConfig ) ;
19
- const apiBaseUrl = useApiBaseUrl ( siteConfig ) ;
26
+ const { baseUrl : apiBaseUrl , authUrl , redirectUri } = useApiBaseUrl ( siteConfig ) ;
20
27
const config = useGoogleOneTapConfig ( apiBaseUrl , debugLogger ) ;
21
28
const { authStatus } = useAuthStatus ( siteConfig , debugLogger ) ;
29
+ const verifyGoogleOneTap = useGoogleOneTapVerify ( apiBaseUrl , debugLogger ) ;
30
+
31
+ // Function to manually build Logto sign-in URL
32
+ const buildSignInUrl = useCallback (
33
+ ( { oneTimeToken, email, isNewUser } : GoogleOneTapVerifyResponse ) => {
34
+ try {
35
+ const signInUrl = new URL ( authUrl ) ;
36
+
37
+ // Standard OIDC parameters: client_id
38
+ signInUrl . searchParams . set ( 'client_id' , 'admin-console' ) ;
39
+ signInUrl . searchParams . set ( 'redirect_uri' , redirectUri ) ;
40
+ signInUrl . searchParams . set ( 'first_screen' , isNewUser ? 'register' : 'sign_in' ) ;
41
+
42
+ // Add one-time token parameters
43
+ signInUrl . searchParams . set ( 'one_time_token' , oneTimeToken ) ;
44
+ signInUrl . searchParams . set ( 'login_hint' , email ) ;
45
+
46
+ return signInUrl . toString ( ) ;
47
+ } catch ( error ) {
48
+ debugLogger . error ( 'Failed to build sign-in URL:' , error ) ;
49
+ return null ;
50
+ }
51
+ } ,
52
+ [ authUrl , redirectUri , debugLogger ]
53
+ ) ;
54
+
55
+ const handleCredentialResponse = useCallback (
56
+ async ( response : GoogleOneTapCredentialResponse ) => {
57
+ debugLogger . log ( 'handleCredentialResponse received response:' , response ) ;
58
+
59
+ const verifyData = await verifyGoogleOneTap ( response ) ;
60
+
61
+ if ( verifyData ) {
62
+ debugLogger . log ( 'Verification completed:' , verifyData ) ;
63
+
64
+ try {
65
+ // Build Logto sign-in URL with one-time token
66
+ const signInUrl = buildSignInUrl ( verifyData ) ;
67
+
68
+ if ( signInUrl ) {
69
+ // Open sign-in URL in new tab
70
+ window . open ( signInUrl , '_blank' , 'noopener,noreferrer' ) ;
71
+ debugLogger . log ( 'Logto sign-in URL opened in new tab with one-time token' ) ;
72
+ } else {
73
+ debugLogger . error ( 'Failed to build sign-in URL' ) ;
74
+ }
75
+ } catch ( error ) {
76
+ debugLogger . error ( 'Failed to open sign-in URL:' , error ) ;
77
+ }
78
+ }
79
+ } ,
80
+ [ verifyGoogleOneTap , debugLogger , buildSignInUrl ]
81
+ ) ;
82
+
83
+ // Make handleCredentialResponse globally available for Google One Tap callback
84
+ useEffect ( ( ) => {
85
+ if ( typeof window !== 'undefined' ) {
86
+ // eslint-disable-next-line @silverhand/fp/no-mutation, no-restricted-syntax
87
+ ( window as unknown as Record < string , unknown > ) . handleCredentialResponse =
88
+ handleCredentialResponse ;
89
+ }
90
+ } , [ handleCredentialResponse ] ) ;
22
91
23
92
debugLogger . log ( 'config' , config ) ;
24
93
debugLogger . log ( 'authStatus' , authStatus ) ;
0 commit comments