52 lines
1.5 KiB
JavaScript
52 lines
1.5 KiB
JavaScript
import { useEffect } from 'react'
|
|
import { useLocation, useNavigate } from 'react-router-dom'
|
|
|
|
import { hasSeenOnboarding, isNativeApp } from '../utils/Onboarding'
|
|
import { setPendingInvite } from '../utils/PendingInvite'
|
|
|
|
// Routes a first-run user may legitimately be on without having gone through
|
|
// onboarding: the flow itself, deep-link auth callbacks, and the legal pages
|
|
// linked from it.
|
|
const ALLOWED_PATHS = [
|
|
'/onboarding',
|
|
'/get-started',
|
|
'/login/settings',
|
|
'/privacy',
|
|
'/terms',
|
|
// An invite link is a legitimate first launch: the join view explains itself
|
|
// and routes to sign-in, so onboarding must not swallow the code.
|
|
'/circle/join',
|
|
]
|
|
|
|
const isAllowed = pathname =>
|
|
ALLOWED_PATHS.includes(pathname) || pathname.startsWith('/auth/')
|
|
|
|
/**
|
|
* Sends first-launch native users to the onboarding flow. Runs on every
|
|
* navigation because an expired session hard-redirects to /login through
|
|
* `window.location`, which remounts the app.
|
|
*/
|
|
const useOnboardingGate = () => {
|
|
const navigate = useNavigate()
|
|
const { pathname, search } = useLocation()
|
|
const isRedirecting =
|
|
isNativeApp() &&
|
|
!hasSeenOnboarding() &&
|
|
!localStorage.getItem('token') &&
|
|
!isAllowed(pathname)
|
|
|
|
useEffect(() => {
|
|
if (!isRedirecting) return
|
|
|
|
if (pathname === '/circle/join') {
|
|
setPendingInvite(new URLSearchParams(search).get('code'))
|
|
}
|
|
|
|
navigate('/onboarding', { replace: true })
|
|
}, [isRedirecting, pathname, search, navigate])
|
|
|
|
return isRedirecting
|
|
}
|
|
|
|
export default useOnboardingGate
|