11import '../App.css'
22
3- import { useEffect , useMemo , useState } from 'react'
3+ import { useEffect , useMemo , useRef , useState } from 'react'
44import { Link , useNavigate } from 'react-router-dom'
55
66import { issueWorkspaceToken , type WorkspaceIssue } from '../api'
@@ -31,9 +31,16 @@ export default function OnboardingTokenPage() {
3131 const [ loading , setLoading ] = useState ( true )
3232 const [ error , setError ] = useState < string | null > ( null )
3333 const [ issued , setIssued ] = useState < WorkspaceIssue | null > ( null )
34+ const [ pairStatus , setPairStatus ] = useState < string | null > ( null )
35+ const pairTimeoutRef = useRef < number | null > ( null )
3436
3537 const token = issued ?. workspace_token ?? ''
3638 const expiresAt = issued ?. expires_at ?? ''
39+ const savedToken = useMemo ( ( ) => {
40+ const raw = localStorage . getItem ( 'easyrelocate_workspace_token' )
41+ return ( raw ?? '' ) . trim ( )
42+ } , [ issued ] )
43+ const effectiveToken = token || savedToken
3744
3845 const alreadyHasToken = useMemo ( ( ) => {
3946 const raw = localStorage . getItem ( 'easyrelocate_workspace_token' )
@@ -96,6 +103,48 @@ export default function OnboardingTokenPage() {
96103 navigate ( '/compare' )
97104 }
98105
106+ const onPairExtension = ( ) => {
107+ const t = effectiveToken . trim ( )
108+ if ( ! t ) {
109+ setPairStatus ( 'Missing token to pair.' )
110+ return
111+ }
112+ setPairStatus ( 'Waiting for extension…' )
113+ if ( pairTimeoutRef . current ) {
114+ window . clearTimeout ( pairTimeoutRef . current )
115+ }
116+ pairTimeoutRef . current = window . setTimeout ( ( ) => {
117+ setPairStatus ( 'No response from extension. Make sure it is installed and enabled.' )
118+ } , 3000 )
119+ window . postMessage ( { type : 'EASYRELOCATE_PAIR_REQUEST' , token : t } , window . location . origin )
120+ }
121+
122+ useEffect ( ( ) => {
123+ const handler = ( event : MessageEvent ) => {
124+ if ( event . source !== window ) return
125+ if ( event . origin !== window . location . origin ) return
126+ const data = event . data as { type ?: string ; ok ?: boolean ; error ?: string } | null
127+ if ( ! data || data . type !== 'EASYRELOCATE_PAIR_RESULT' ) return
128+ if ( pairTimeoutRef . current ) {
129+ window . clearTimeout ( pairTimeoutRef . current )
130+ pairTimeoutRef . current = null
131+ }
132+ if ( data . ok ) {
133+ setPairStatus ( 'Extension paired successfully.' )
134+ } else {
135+ setPairStatus ( data . error || 'Failed to pair with extension.' )
136+ }
137+ }
138+ window . addEventListener ( 'message' , handler )
139+ return ( ) => {
140+ window . removeEventListener ( 'message' , handler )
141+ if ( pairTimeoutRef . current ) {
142+ window . clearTimeout ( pairTimeoutRef . current )
143+ pairTimeoutRef . current = null
144+ }
145+ }
146+ } , [ ] )
147+
99148 return (
100149 < div className = "landing" >
101150 < header className = "landingHeader" >
@@ -173,6 +222,9 @@ export default function OnboardingTokenPage() {
173222 < button className = "button secondary" onClick = { ( ) => void onCopy ( ) } >
174223 Copy
175224 </ button >
225+ < button className = "button secondary" onClick = { onPairExtension } >
226+ Pair extension
227+ </ button >
176228 < button className = "button" onClick = { onContinue } >
177229 Continue to map
178230 </ button >
@@ -185,11 +237,18 @@ export default function OnboardingTokenPage() {
185237 < button className = "button secondary" onClick = { ( ) => void doIssue ( ) } >
186238 Generate token
187239 </ button >
240+ < button className = "button secondary" onClick = { onPairExtension } >
241+ Pair extension
242+ </ button >
188243 < button className = "button" onClick = { onContinue } >
189244 Continue to map
190245 </ button >
191246 </ div >
192247 ) : null }
248+
249+ { pairStatus ? (
250+ < div style = { { marginTop : 12 , color : '#475569' , fontSize : 13 } } > { pairStatus } </ div >
251+ ) : null }
193252 </ div >
194253 </ section >
195254 </ main >
0 commit comments