fix(callback): login and private route redirect

Fix #229
This commit is contained in:
en 2025-01-31 19:32:28 +01:00
parent efe3c2c9c7
commit 37baf57093
6 changed files with 97 additions and 84 deletions

View File

@ -4,8 +4,6 @@ import { Navigate, Route, Routes } from 'react-router-dom'
import { useAppSelector, useAuth } from './hooks'
import { MainLayout } from './layouts/Main'
import { appPrivateRoutes, appPublicRoutes } from './routes'
import {
privateRoutes,
publicRoutes,
@ -16,7 +14,7 @@ import './App.scss'
const App = () => {
const { checkSession } = useAuth()
const authState = useAppSelector((state) => state.auth)
const isLoggedIn = useAppSelector((state) => state.auth?.loggedIn)
useEffect(() => {
if (window.location.hostname === '0.0.0.0') {
@ -29,19 +27,9 @@ const App = () => {
checkSession()
}, [checkSession])
const handleRootRedirect = () => {
if (authState.loggedIn) return appPrivateRoutes.homePage
const callbackPathEncoded = btoa(
window.location.href.split(`${window.location.origin}/#`)[1]
)
return `${appPublicRoutes.landingPage}?callbackPath=${callbackPathEncoded}`
}
// Hide route only if loggedIn and r.hiddenWhenLoggedIn are both true
const publicRoutesList = recursiveRouteRenderer(publicRoutes, (r) => {
return !authState.loggedIn || !r.hiddenWhenLoggedIn
return !isLoggedIn || !r.hiddenWhenLoggedIn
})
const privateRouteList = recursiveRouteRenderer(privateRoutes)
@ -49,9 +37,9 @@ const App = () => {
return (
<Routes>
<Route element={<MainLayout />}>
{authState?.loggedIn && privateRouteList}
{publicRoutesList}
<Route path="*" element={<Navigate to={handleRootRedirect()} />} />
{privateRouteList}
<Route path="*" element={<Navigate to={'/'} />} />
</Route>
</Routes>
)

View File

@ -1,16 +1,11 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { Outlet, useNavigate, useSearchParams } from 'react-router-dom'
import { getPublicKey, nip19 } from 'nostr-tools'
import { init as initNostrLogin } from 'nostr-login'
import { NostrLoginAuthOptions } from 'nostr-login/dist/types'
import { AppBar } from '../components/AppBar/AppBar'
import { LoadingSpinner } from '../components/LoadingSpinner'
import { NostrController } from '../controllers'
import {
useAppDispatch,
useAppSelector,
@ -19,7 +14,6 @@ import {
useNDK,
useNDKContext
} from '../hooks'
import {
restoreState,
setUserProfile,
@ -30,9 +24,7 @@ import {
setUserRobotImage
} from '../store/actions'
import { LoginMethod } from '../store/auth/types'
import { getRoboHashPicture, loadState } from '../utils'
import styles from './style.module.scss'
export const MainLayout = () => {
@ -53,29 +45,32 @@ export const MainLayout = () => {
// Ref to track if `subscribeForSigits` has been called
const hasSubscribed = useRef(false)
const navigateAfterLogin = (path: string) => {
const callbackPath = searchParams.get('callbackPath')
if (callbackPath) {
// base64 decoded path
const path = atob(callbackPath)
const navigateAfterLogin = useCallback(
(path: string) => {
const isCallback = window.location.hash.startsWith('#/?callbackPath=')
if (isCallback) {
const path = atob(window.location.hash.replace('#/?callbackPath=', ''))
setSearchParams((prev) => {
prev.delete('callbackPath')
return prev
})
navigate(path)
return
}
navigate(path)
return
}
navigate(path)
}
},
[navigate, setSearchParams]
)
const login = useCallback(async () => {
dispatch(updateLoginMethod(LoginMethod.nostrLogin))
const nostrController = NostrController.getInstance()
const pubkey = await nostrController.capturePublicKey()
const redirectPath = await authAndGetMetadataAndRelaysMap(pubkey)
if (redirectPath) {
try {
dispatch(updateLoginMethod(LoginMethod.nostrLogin))
const nostrController = NostrController.getInstance()
const pubkey = await nostrController.capturePublicKey()
const redirectPath = await authAndGetMetadataAndRelaysMap(pubkey)
navigateAfterLogin(redirectPath)
} catch (error) {
console.error(`Error occured during login`, error)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dispatch])

View File

@ -1,7 +1,5 @@
import { Box, Button } from '@mui/material'
import { useEffect } from 'react'
import { Outlet, useLocation } from 'react-router-dom'
import { saveVisitedLink } from '../../utils'
import { Outlet } from 'react-router-dom'
import { CardComponent } from '../../components/Landing/CardComponent/CardComponent'
import { Container } from '../../components/Container'
import styles from './style.module.scss'
@ -20,13 +18,19 @@ import {
import { FontAwesomeIconStack } from '../../components/FontAwesomeIconStack'
import { Footer } from '../../components/Footer/Footer'
import { launch as launchNostrLoginDialog } from 'nostr-login'
import { useDidMount } from '../../hooks'
export const LandingPage = () => {
const location = useLocation()
const onSignInClick = async () => {
launchNostrLoginDialog()
}
useDidMount(() => {
const isCallback = window.location.hash.startsWith('#/?callbackPath=')
// Open nostr login if detect callback
if (isCallback) {
onSignInClick()
}
})
const cards = [
{
@ -101,10 +105,6 @@ export const LandingPage = () => {
}
]
useEffect(() => {
saveVisitedLink(location.pathname, location.search)
}, [location])
return (
<div className={styles.background}>
<div

View File

@ -0,0 +1,21 @@
import { Navigate, useLocation } from 'react-router-dom'
import { useAppSelector } from '../hooks'
import { appPublicRoutes } from '.'
export function PrivateRoute({ children }: { children: JSX.Element }) {
const location = useLocation()
const isLoggedIn = useAppSelector((state) => state.auth?.loggedIn)
if (!isLoggedIn) {
return (
<Navigate
to={{
pathname: appPublicRoutes.landingPage,
search: `?callbackPath=${btoa(location.pathname)}`
}}
replace
/>
)
}
return children
}

View File

@ -11,6 +11,7 @@ import { RelaysPage } from '../pages/settings/relays'
import { SettingsPage } from '../pages/settings/Settings'
import { SignPage } from '../pages/sign'
import { VerifyPage } from '../pages/verify'
import { PrivateRoute } from './PrivateRoute'
/**
* Helper type allows for extending react-router-dom's **RouteProps** with generic type
@ -70,34 +71,66 @@ export const publicRoutes: PublicRouteProps[] = [
export const privateRoutes = [
{
path: appPrivateRoutes.homePage,
element: <HomePage />
element: (
<PrivateRoute>
<HomePage />
</PrivateRoute>
)
},
{
path: appPrivateRoutes.create,
element: <CreatePage />
element: (
<PrivateRoute>
<CreatePage />
</PrivateRoute>
)
},
{
path: `${appPrivateRoutes.sign}/:id?`,
element: <SignPage />
element: (
<PrivateRoute>
<SignPage />
</PrivateRoute>
)
},
{
path: appPrivateRoutes.settings,
element: <SettingsPage />
element: (
<PrivateRoute>
<SettingsPage />
</PrivateRoute>
)
},
{
path: appPrivateRoutes.profileSettings,
element: <ProfileSettingsPage />
element: (
<PrivateRoute>
<ProfileSettingsPage />
</PrivateRoute>
)
},
{
path: appPrivateRoutes.cacheSettings,
element: <CacheSettingsPage />
element: (
<PrivateRoute>
<CacheSettingsPage />
</PrivateRoute>
)
},
{
path: appPrivateRoutes.relays,
element: <RelaysPage />
element: (
<PrivateRoute>
<RelaysPage />
</PrivateRoute>
)
},
{
path: appPrivateRoutes.nostrLogin,
element: <NostrLoginPage />
element: (
<PrivateRoute>
<NostrLoginPage />
</PrivateRoute>
)
}
]

View File

@ -26,30 +26,6 @@ export const clearState = () => {
localStorage.removeItem('state')
}
export const saveVisitedLink = (pathname: string, search: string) => {
localStorage.setItem(
'visitedLink',
JSON.stringify({
pathname,
search
})
)
}
export const getVisitedLink = () => {
const visitedLink = localStorage.getItem('visitedLink')
if (!visitedLink) return null
try {
return JSON.parse(visitedLink) as {
pathname: string
search: string
}
} catch {
return null
}
}
export const saveAuthToken = (token: string) => {
localStorage.setItem('authToken', token)
}