From 37baf5709397e7099a735d6390cfceffff646f11 Mon Sep 17 00:00:00 2001 From: en Date: Fri, 31 Jan 2025 19:32:28 +0100 Subject: [PATCH] fix(callback): login and private route redirect Fix #229 --- src/App.tsx | 20 +++------------ src/layouts/Main.tsx | 49 +++++++++++++++++-------------------- src/pages/landing/index.tsx | 18 +++++++------- src/routes/PrivateRoute.tsx | 21 ++++++++++++++++ src/routes/util.tsx | 49 +++++++++++++++++++++++++++++++------ src/utils/localStorage.ts | 24 ------------------ 6 files changed, 97 insertions(+), 84 deletions(-) create mode 100644 src/routes/PrivateRoute.tsx diff --git a/src/App.tsx b/src/App.tsx index 3829ba6..d10dc0d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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 ( }> - {authState?.loggedIn && privateRouteList} {publicRoutesList} - } /> + {privateRouteList} + } /> ) diff --git a/src/layouts/Main.tsx b/src/layouts/Main.tsx index 85daf75..c91c986 100644 --- a/src/layouts/Main.tsx +++ b/src/layouts/Main.tsx @@ -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]) diff --git a/src/pages/landing/index.tsx b/src/pages/landing/index.tsx index 773b923..5d24bdf 100644 --- a/src/pages/landing/index.tsx +++ b/src/pages/landing/index.tsx @@ -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 (
state.auth?.loggedIn) + if (!isLoggedIn) { + return ( + + ) + } + + return children +} diff --git a/src/routes/util.tsx b/src/routes/util.tsx index 8773b81..2e1be26 100644 --- a/src/routes/util.tsx +++ b/src/routes/util.tsx @@ -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: + element: ( + + + + ) }, { path: appPrivateRoutes.create, - element: + element: ( + + + + ) }, { path: `${appPrivateRoutes.sign}/:id?`, - element: + element: ( + + + + ) }, { path: appPrivateRoutes.settings, - element: + element: ( + + + + ) }, { path: appPrivateRoutes.profileSettings, - element: + element: ( + + + + ) }, { path: appPrivateRoutes.cacheSettings, - element: + element: ( + + + + ) }, { path: appPrivateRoutes.relays, - element: + element: ( + + + + ) }, { path: appPrivateRoutes.nostrLogin, - element: + element: ( + + + + ) } ] diff --git a/src/utils/localStorage.ts b/src/utils/localStorage.ts index 472e092..8196e35 100644 --- a/src/utils/localStorage.ts +++ b/src/utils/localStorage.ts @@ -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) }