Keep App component loaded while navigating (#247)
* Make /settings not throw away App component * Make App not reload for Onboarding * Close sidebar when navigating to /settings * Use centralized constants for route pathnames * Clean up a few stray raw path literals
This commit is contained in:
@ -13,7 +13,7 @@
|
|||||||
<link rel="manifest" href="/manifest.json" />
|
<link rel="manifest" href="/manifest.json" />
|
||||||
<title>KittyCAD Modeling App</title>
|
<title>KittyCAD Modeling App</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="body-bg">
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root" class="h-screen overflow-y-auto"></div>
|
<div id="root" class="h-screen overflow-y-auto"></div>
|
||||||
<script type="module" src="/src/index.tsx"></script>
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
|
@ -6,6 +6,7 @@ import { useNavigate } from 'react-router-dom'
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { isTauri } from './lib/isTauri'
|
import { isTauri } from './lib/isTauri'
|
||||||
import Loading from './components/Loading'
|
import Loading from './components/Loading'
|
||||||
|
import { paths } from './Router'
|
||||||
|
|
||||||
// Wrapper around protected routes, used in src/Router.tsx
|
// Wrapper around protected routes, used in src/Router.tsx
|
||||||
export const Auth = ({ children }: React.PropsWithChildren) => {
|
export const Auth = ({ children }: React.PropsWithChildren) => {
|
||||||
@ -27,7 +28,7 @@ export const Auth = ({ children }: React.PropsWithChildren) => {
|
|||||||
(isTauri() && !token) ||
|
(isTauri() && !token) ||
|
||||||
(!isTauri() && !isLoading && !(user && 'id' in user))
|
(!isTauri() && !isLoading && !(user && 'id' in user))
|
||||||
) {
|
) {
|
||||||
navigate('/signin')
|
navigate(paths.SIGN_IN)
|
||||||
}
|
}
|
||||||
}, [user, token, navigate, isLoading])
|
}, [user, token, navigate, isLoading])
|
||||||
|
|
||||||
|
@ -1,52 +1,84 @@
|
|||||||
import { App } from './App'
|
import { App } from './App'
|
||||||
import { createBrowserRouter, redirect, RouterProvider } from 'react-router-dom'
|
import {
|
||||||
|
createBrowserRouter,
|
||||||
|
Outlet,
|
||||||
|
redirect,
|
||||||
|
RouterProvider,
|
||||||
|
} from 'react-router-dom'
|
||||||
import { ErrorPage } from './components/ErrorPage'
|
import { ErrorPage } from './components/ErrorPage'
|
||||||
import { Settings } from './routes/Settings'
|
import { Settings } from './routes/Settings'
|
||||||
import Onboarding, { onboardingRoutes } from './routes/Onboarding'
|
import Onboarding, {
|
||||||
|
onboardingRoutes,
|
||||||
|
onboardingPaths,
|
||||||
|
} from './routes/Onboarding'
|
||||||
import SignIn from './routes/SignIn'
|
import SignIn from './routes/SignIn'
|
||||||
import { Auth } from './Auth'
|
import { Auth } from './Auth'
|
||||||
|
|
||||||
|
const prependRoutes =
|
||||||
|
(routesObject: Record<string, string>) => (prepend: string) => {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(routesObject).map(([constName, path]) => [
|
||||||
|
constName,
|
||||||
|
prepend + path,
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const paths = {
|
||||||
|
INDEX: '/',
|
||||||
|
SETTINGS: '/settings',
|
||||||
|
SIGN_IN: '/signin',
|
||||||
|
ONBOARDING: prependRoutes(onboardingPaths)(
|
||||||
|
'/onboarding/'
|
||||||
|
) as typeof onboardingPaths,
|
||||||
|
}
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
path: '/',
|
path: paths.INDEX,
|
||||||
element: (
|
element: (
|
||||||
<Auth>
|
<Auth>
|
||||||
|
<Outlet />
|
||||||
<App />
|
<App />
|
||||||
</Auth>
|
</Auth>
|
||||||
),
|
),
|
||||||
errorElement: <ErrorPage />,
|
errorElement: <ErrorPage />,
|
||||||
loader: () => {
|
loader: ({ request }) => {
|
||||||
const store = localStorage.getItem('store')
|
const store = localStorage.getItem('store')
|
||||||
if (store === null) {
|
if (store === null) {
|
||||||
return redirect('/onboarding')
|
return redirect(paths.ONBOARDING.INDEX)
|
||||||
} else {
|
} else {
|
||||||
const status = JSON.parse(store).state.onboardingStatus
|
const status = JSON.parse(store).state.onboardingStatus || ''
|
||||||
if (status !== 'done' && status !== 'dismissed') {
|
const notEnRouteToOnboarding =
|
||||||
return redirect('/onboarding/' + status)
|
!request.url.includes(paths.ONBOARDING.INDEX) &&
|
||||||
|
request.method === 'GET'
|
||||||
|
// '' is the initial state, 'done' and 'dismissed' are the final states
|
||||||
|
const hasValidOnboardingStatus =
|
||||||
|
(status !== undefined && status.length === 0) ||
|
||||||
|
!(status === 'done' || status === 'dismissed')
|
||||||
|
const shouldRedirectToOnboarding =
|
||||||
|
notEnRouteToOnboarding && hasValidOnboardingStatus
|
||||||
|
|
||||||
|
if (shouldRedirectToOnboarding) {
|
||||||
|
return redirect(paths.ONBOARDING.INDEX + status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: paths.SETTINGS,
|
||||||
|
element: <Settings />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: paths.ONBOARDING.INDEX,
|
||||||
|
element: <Onboarding />,
|
||||||
|
children: onboardingRoutes,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings',
|
path: paths.SIGN_IN,
|
||||||
element: (
|
|
||||||
<Auth>
|
|
||||||
<Settings />
|
|
||||||
</Auth>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/onboarding',
|
|
||||||
element: (
|
|
||||||
<Auth>
|
|
||||||
<Onboarding />
|
|
||||||
</Auth>
|
|
||||||
),
|
|
||||||
children: onboardingRoutes,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/signin',
|
|
||||||
element: <SignIn />,
|
element: <SignIn />,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { paths } from '../Router'
|
||||||
|
|
||||||
interface ActionButtonProps extends React.PropsWithChildren {
|
interface ActionButtonProps extends React.PropsWithChildren {
|
||||||
icon?: ActionIconProps
|
icon?: ActionIconProps
|
||||||
@ -17,7 +18,7 @@ export const ActionButton = ({
|
|||||||
icon,
|
icon,
|
||||||
className,
|
className,
|
||||||
onClick,
|
onClick,
|
||||||
to = '/',
|
to = paths.INDEX,
|
||||||
Element = 'button',
|
Element = 'button',
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
|
@ -2,6 +2,7 @@ import { Link } from 'react-router-dom'
|
|||||||
import { Toolbar } from '../Toolbar'
|
import { Toolbar } from '../Toolbar'
|
||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
import UserSidebarMenu from './UserSidebarMenu'
|
import UserSidebarMenu from './UserSidebarMenu'
|
||||||
|
import { paths } from '../Router'
|
||||||
|
|
||||||
interface AppHeaderProps extends React.PropsWithChildren {
|
interface AppHeaderProps extends React.PropsWithChildren {
|
||||||
showToolbar?: boolean
|
showToolbar?: boolean
|
||||||
@ -24,7 +25,7 @@ export const AppHeader = ({
|
|||||||
className
|
className
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Link to="/">
|
<Link to={paths.INDEX}>
|
||||||
<img
|
<img
|
||||||
src="/kitt-arcade-winking.svg"
|
src="/kitt-arcade-winking.svg"
|
||||||
alt="KittyCAD App"
|
alt="KittyCAD App"
|
||||||
|
@ -4,6 +4,7 @@ import { ActionButton } from './ActionButton'
|
|||||||
import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
|
import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { paths } from '../Router'
|
||||||
|
|
||||||
const UserSidebarMenu = ({ user }: { user?: User }) => {
|
const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||||
const displayedName = getDisplayName(user)
|
const displayedName = getDisplayName(user)
|
||||||
@ -58,61 +59,72 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
<Popover.Overlay className="fixed z-40 inset-0 bg-chalkboard-110/50" />
|
<Popover.Overlay className="fixed z-40 inset-0 bg-chalkboard-110/50" />
|
||||||
|
|
||||||
<Popover.Panel className="fixed inset-0 left-auto z-50 w-64 bg-chalkboard-10 dark:bg-chalkboard-100 border border-liquid-100 shadow-md rounded-l-lg">
|
<Popover.Panel className="fixed inset-0 left-auto z-50 w-64 bg-chalkboard-10 dark:bg-chalkboard-100 border border-liquid-100 shadow-md rounded-l-lg">
|
||||||
{user && (
|
{({ close }) => (
|
||||||
<div className="flex items-center gap-4 px-4 py-3 bg-liquid-100">
|
<>
|
||||||
{user.image && !imageLoadFailed && (
|
{user && (
|
||||||
<div className="rounded-full shadow-inner overflow-hidden">
|
<div className="flex items-center gap-4 px-4 py-3 bg-liquid-100">
|
||||||
<img
|
{user.image && !imageLoadFailed && (
|
||||||
src={user.image}
|
<div className="rounded-full shadow-inner overflow-hidden">
|
||||||
alt={user.name || ''}
|
<img
|
||||||
className="h-8 w-8"
|
src={user.image}
|
||||||
referrerPolicy="no-referrer"
|
alt={user.name || ''}
|
||||||
onError={() => setImageLoadFailed(true)}
|
className="h-8 w-8"
|
||||||
/>
|
referrerPolicy="no-referrer"
|
||||||
|
onError={() => setImageLoadFailed(true)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p
|
||||||
|
className="m-0 text-liquid-10 text-mono"
|
||||||
|
data-testid="username"
|
||||||
|
>
|
||||||
|
{displayedName || ''}
|
||||||
|
</p>
|
||||||
|
{displayedName !== user.email && (
|
||||||
|
<p
|
||||||
|
className="m-0 text-liquid-40 text-xs"
|
||||||
|
data-testid="email"
|
||||||
|
>
|
||||||
|
{user.email}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div className="p-4 flex flex-col gap-2">
|
||||||
<div>
|
<ActionButton
|
||||||
<p
|
icon={{ icon: faGear }}
|
||||||
className="m-0 text-liquid-10 text-mono"
|
className="border-transparent dark:border-transparent dark:hover:border-liquid-60"
|
||||||
data-testid="username"
|
onClick={() => {
|
||||||
|
// since /settings is a nested route the sidebar doesn't close
|
||||||
|
// automatically when navigating to it
|
||||||
|
close()
|
||||||
|
navigate(paths.SETTINGS)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{displayedName || ''}
|
Settings
|
||||||
</p>
|
</ActionButton>
|
||||||
{displayedName !== user.email && (
|
<ActionButton
|
||||||
<p className="m-0 text-liquid-40 text-xs" data-testid="email">
|
Element="button"
|
||||||
{user.email}
|
onClick={() => {
|
||||||
</p>
|
setToken('')
|
||||||
)}
|
navigate(paths.SIGN_IN)
|
||||||
|
}}
|
||||||
|
icon={{
|
||||||
|
icon: faSignOutAlt,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="border-transparent dark:border-transparent hover:border-destroy-40 dark:hover:border-destroy-60"
|
||||||
|
>
|
||||||
|
Sign out
|
||||||
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="p-4 flex flex-col gap-2">
|
|
||||||
<ActionButton
|
|
||||||
Element="link"
|
|
||||||
icon={{ icon: faGear }}
|
|
||||||
to="/settings"
|
|
||||||
className="border-transparent dark:border-transparent dark:hover:border-liquid-60"
|
|
||||||
>
|
|
||||||
Settings
|
|
||||||
</ActionButton>
|
|
||||||
<ActionButton
|
|
||||||
Element="button"
|
|
||||||
onClick={() => {
|
|
||||||
setToken('')
|
|
||||||
navigate('/signin')
|
|
||||||
}}
|
|
||||||
icon={{
|
|
||||||
icon: faSignOutAlt,
|
|
||||||
bgClassName: 'bg-destroy-80',
|
|
||||||
iconClassName:
|
|
||||||
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
|
||||||
}}
|
|
||||||
className="border-transparent dark:border-transparent hover:border-destroy-40 dark:hover:border-destroy-60"
|
|
||||||
>
|
|
||||||
Sign out
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
|
||||||
</Popover.Panel>
|
</Popover.Panel>
|
||||||
</Popover>
|
</Popover>
|
||||||
)
|
)
|
||||||
|
@ -11,15 +11,24 @@ body {
|
|||||||
sans-serif;
|
sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
@apply text-chalkboard-110 bg-chalkboard-10;
|
@apply text-chalkboard-110;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: var(--color-chalkboard-20) var(--color-chalkboard-40);
|
scrollbar-color: var(--color-chalkboard-20) var(--color-chalkboard-40);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.body-bg {
|
||||||
|
@apply bg-chalkboard-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body-bg.dark,
|
||||||
|
.dark .body-bg {
|
||||||
|
@apply bg-chalkboard-100;
|
||||||
|
}
|
||||||
|
|
||||||
body.dark {
|
body.dark {
|
||||||
scrollbar-color: var(--color-chalkboard-70) var(--color-chalkboard-90);
|
scrollbar-color: var(--color-chalkboard-70) var(--color-chalkboard-90);
|
||||||
@apply bg-chalkboard-100 text-chalkboard-10;
|
@apply text-chalkboard-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
|
@ -1,29 +1,36 @@
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { Outlet, useNavigate } from 'react-router-dom'
|
import { Outlet, useNavigate } from 'react-router-dom'
|
||||||
import { useStore } from '../../useStore'
|
import { useStore } from '../../useStore'
|
||||||
import { App } from '../../App'
|
|
||||||
|
|
||||||
import Introduction from './Introduction'
|
import Introduction from './Introduction'
|
||||||
import Units from './Units'
|
import Units from './Units'
|
||||||
import Camera from './Camera'
|
import Camera from './Camera'
|
||||||
import Sketching from './Sketching'
|
import Sketching from './Sketching'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
import { paths } from '../../Router'
|
||||||
|
|
||||||
|
export const onboardingPaths = {
|
||||||
|
INDEX: '',
|
||||||
|
UNITS: 'units',
|
||||||
|
CAMERA: 'camera',
|
||||||
|
SKETCHING: 'sketching',
|
||||||
|
}
|
||||||
|
|
||||||
export const onboardingRoutes = [
|
export const onboardingRoutes = [
|
||||||
{
|
{
|
||||||
path: '',
|
index: true,
|
||||||
element: <Introduction />,
|
element: <Introduction />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'units',
|
path: onboardingPaths.UNITS,
|
||||||
element: <Units />,
|
element: <Units />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'camera',
|
path: onboardingPaths.CAMERA,
|
||||||
element: <Camera />,
|
element: <Camera />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'sketching',
|
path: onboardingPaths.SKETCHING,
|
||||||
element: <Sketching />,
|
element: <Sketching />,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -48,7 +55,7 @@ export function useDismiss() {
|
|||||||
|
|
||||||
return useCallback(() => {
|
return useCallback(() => {
|
||||||
setOnboardingStatus('dismissed')
|
setOnboardingStatus('dismissed')
|
||||||
navigate('/')
|
navigate(paths.INDEX)
|
||||||
}, [setOnboardingStatus, navigate])
|
}, [setOnboardingStatus, navigate])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +66,6 @@ const Onboarding = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<App />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,11 @@ import { toast } from 'react-hot-toast'
|
|||||||
import { Toggle } from '../components/Toggle/Toggle'
|
import { Toggle } from '../components/Toggle/Toggle'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
|
import { paths } from '../Router'
|
||||||
|
|
||||||
export const Settings = () => {
|
export const Settings = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
useHotkeys('esc', () => navigate('/'))
|
useHotkeys('esc', () => navigate(paths.INDEX))
|
||||||
const {
|
const {
|
||||||
defaultDir,
|
defaultDir,
|
||||||
setDefaultDir,
|
setDefaultDir,
|
||||||
@ -50,7 +51,7 @@ export const Settings = () => {
|
|||||||
async function handleDirectorySelection() {
|
async function handleDirectorySelection() {
|
||||||
const newDirectory = await open({
|
const newDirectory = await open({
|
||||||
directory: true,
|
directory: true,
|
||||||
defaultPath: (defaultDir.base || '') + (defaultDir.dir || '/'),
|
defaultPath: (defaultDir.base || '') + (defaultDir.dir || paths.INDEX),
|
||||||
title: 'Choose a new default directory',
|
title: 'Choose a new default directory',
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -60,11 +61,11 @@ export const Settings = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="body-bg fixed inset-0 z-40 overflow-auto">
|
||||||
<AppHeader showToolbar={false}>
|
<AppHeader showToolbar={false}>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="link"
|
Element="link"
|
||||||
to="/"
|
to={paths.INDEX}
|
||||||
icon={{
|
icon={{
|
||||||
icon: faXmark,
|
icon: faXmark,
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
@ -211,7 +212,7 @@ export const Settings = () => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOnboardingStatus('')
|
setOnboardingStatus('')
|
||||||
navigate('/')
|
navigate(paths.ONBOARDING.INDEX)
|
||||||
}}
|
}}
|
||||||
icon={{ icon: faArrowRotateBack }}
|
icon={{ icon: faArrowRotateBack }}
|
||||||
>
|
>
|
||||||
@ -219,7 +220,7 @@ export const Settings = () => {
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import { invoke } from '@tauri-apps/api/tauri'
|
|||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { VITE_KC_SITE_BASE_URL, VITE_KC_API_BASE_URL } from '../env'
|
import { VITE_KC_SITE_BASE_URL, VITE_KC_API_BASE_URL } from '../env'
|
||||||
import { getSystemTheme } from '../lib/getSystemTheme'
|
import { getSystemTheme } from '../lib/getSystemTheme'
|
||||||
|
import { paths } from '../Router'
|
||||||
|
|
||||||
const SignIn = () => {
|
const SignIn = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -21,7 +22,7 @@ const SignIn = () => {
|
|||||||
host: VITE_KC_API_BASE_URL,
|
host: VITE_KC_API_BASE_URL,
|
||||||
})
|
})
|
||||||
setToken(token)
|
setToken(token)
|
||||||
navigate('/')
|
navigate(paths.INDEX)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('login button', error)
|
console.error('login button', error)
|
||||||
}
|
}
|
||||||
@ -69,7 +70,9 @@ const SignIn = () => {
|
|||||||
) : (
|
) : (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="link"
|
Element="link"
|
||||||
to={`${VITE_KC_SITE_BASE_URL}/signin?callbackUrl=${encodeURIComponent(
|
to={`${VITE_KC_SITE_BASE_URL}${
|
||||||
|
paths.SIGN_IN
|
||||||
|
}?callbackUrl=${encodeURIComponent(
|
||||||
typeof window !== 'undefined' &&
|
typeof window !== 'undefined' &&
|
||||||
window.location.href.replace('signin', '')
|
window.location.href.replace('signin', '')
|
||||||
)}`}
|
)}`}
|
||||||
|
Reference in New Issue
Block a user