Refactor: separate authMachine from React (#5110)
* Create a global appMachine
* Strip authMachine of side-effects
* Replace react-bound authMachine use with XState actor use
* Fix import goof
* Register auth commands directly!
* @lf94 feedback: conver `AuthNavigationHandler` to `useAuthNavigation`
* Uh, fix signing out thank you @lf94
* Fix tsc
* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)
* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)"
This reverts commit 8dc50b6a26
.
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@ -25,6 +25,7 @@ import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
|
|||||||
import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher'
|
import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher'
|
||||||
import { maybeWriteToDisk } from 'lib/telemetry'
|
import { maybeWriteToDisk } from 'lib/telemetry'
|
||||||
import { commandBarActor } from 'machines/commandBarMachine'
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
import { useToken } from 'machines/appMachine'
|
||||||
maybeWriteToDisk()
|
maybeWriteToDisk()
|
||||||
.then(() => {})
|
.then(() => {})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
@ -60,8 +61,8 @@ export function App() {
|
|||||||
|
|
||||||
useHotKeyListener()
|
useHotKeyListener()
|
||||||
|
|
||||||
const { auth, settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const token = auth?.context?.token
|
const token = useToken()
|
||||||
|
|
||||||
const coreDumpManager = useMemo(
|
const coreDumpManager = useMemo(
|
||||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
import { useAuthState } from 'machines/appMachine'
|
||||||
import Loading from './components/Loading'
|
import Loading from './components/Loading'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
|
||||||
|
|
||||||
// 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) => {
|
||||||
const { auth } = useSettingsAuthContext()
|
const authState = useAuthState()
|
||||||
const isLoggingIn = auth?.state.matches('checkIfLoggedIn')
|
const isLoggingIn = authState.matches('checkIfLoggedIn')
|
||||||
|
|
||||||
return isLoggingIn ? (
|
return isLoggingIn ? (
|
||||||
<Loading>
|
<Loading>
|
||||||
|
@ -37,7 +37,6 @@ import { KclContextProvider } from 'lang/KclProvider'
|
|||||||
import { ASK_TO_OPEN_QUERY_PARAM, BROWSER_PROJECT_NAME } from 'lib/constants'
|
import { ASK_TO_OPEN_QUERY_PARAM, BROWSER_PROJECT_NAME } from 'lib/constants'
|
||||||
import { CoreDumpManager } from 'lib/coredump'
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
import { codeManager, engineCommandManager } from 'lib/singletons'
|
import { codeManager, engineCommandManager } from 'lib/singletons'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
|
||||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { coreDump } from 'lang/wasm'
|
import { coreDump } from 'lang/wasm'
|
||||||
@ -47,6 +46,7 @@ import { reportRejection } from 'lib/trap'
|
|||||||
import { RouteProvider } from 'components/RouteProvider'
|
import { RouteProvider } from 'components/RouteProvider'
|
||||||
import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
|
import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
|
||||||
import { OpenInDesktopAppHandler } from 'components/OpenInDesktopAppHandler'
|
import { OpenInDesktopAppHandler } from 'components/OpenInDesktopAppHandler'
|
||||||
|
import { useToken } from 'machines/appMachine'
|
||||||
|
|
||||||
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
|
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
|
||||||
|
|
||||||
@ -203,8 +203,7 @@ export const Router = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CoreDump() {
|
function CoreDump() {
|
||||||
const { auth } = useSettingsAuthContext()
|
const token = useToken()
|
||||||
const token = auth?.context?.token
|
|
||||||
const coreDumpManager = useMemo(
|
const coreDumpManager = useMemo(
|
||||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||||
[]
|
[]
|
||||||
|
@ -2,11 +2,11 @@ import { Toolbar } from '../Toolbar'
|
|||||||
import UserSidebarMenu from 'components/UserSidebarMenu'
|
import UserSidebarMenu from 'components/UserSidebarMenu'
|
||||||
import { type IndexLoaderData } from 'lib/types'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
|
||||||
import styles from './AppHeader.module.css'
|
import styles from './AppHeader.module.css'
|
||||||
import { RefreshButton } from 'components/RefreshButton'
|
import { RefreshButton } from 'components/RefreshButton'
|
||||||
import { CommandBarOpenButton } from './CommandBarOpenButton'
|
import { CommandBarOpenButton } from './CommandBarOpenButton'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
|
import { useUser } from 'machines/appMachine'
|
||||||
|
|
||||||
interface AppHeaderProps extends React.PropsWithChildren {
|
interface AppHeaderProps extends React.PropsWithChildren {
|
||||||
showToolbar?: boolean
|
showToolbar?: boolean
|
||||||
@ -24,8 +24,7 @@ export const AppHeader = ({
|
|||||||
style,
|
style,
|
||||||
enableMenu = false,
|
enableMenu = false,
|
||||||
}: AppHeaderProps) => {
|
}: AppHeaderProps) => {
|
||||||
const { auth } = useSettingsAuthContext()
|
const user = useUser()
|
||||||
const user = auth?.context?.user
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { markOnce } from 'lib/performance'
|
import { markOnce } from 'lib/performance'
|
||||||
import { commandBarActor } from 'machines/commandBarMachine'
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
import { useToken } from 'machines/appMachine'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -47,7 +48,8 @@ export const FileMachineProvider = ({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { settings, auth } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
|
const token = useToken()
|
||||||
const projectData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
const projectData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
||||||
const { project, file } = projectData
|
const { project, file } = projectData
|
||||||
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
|
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
|
||||||
@ -297,7 +299,7 @@ export const FileMachineProvider = ({
|
|||||||
const kclCommandMemo = useMemo(
|
const kclCommandMemo = useMemo(
|
||||||
() =>
|
() =>
|
||||||
kclCommands({
|
kclCommands({
|
||||||
authToken: auth?.context?.token ?? '',
|
authToken: token ?? '',
|
||||||
projectData,
|
projectData,
|
||||||
settings: {
|
settings: {
|
||||||
defaultUnit: settings?.context?.modeling.defaultUnit.current ?? 'mm',
|
defaultUnit: settings?.context?.modeling.defaultUnit.current ?? 'mm',
|
||||||
|
@ -27,6 +27,7 @@ import { PROJECT_ENTRYPOINT } from 'lib/constants'
|
|||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { codeManager } from 'lib/singletons'
|
import { codeManager } from 'lib/singletons'
|
||||||
|
import { useToken } from 'machines/appMachine'
|
||||||
|
|
||||||
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
||||||
return []
|
return []
|
||||||
@ -69,8 +70,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
const [isKclLspReady, setIsKclLspReady] = useState(false)
|
const [isKclLspReady, setIsKclLspReady] = useState(false)
|
||||||
const [isCopilotLspReady, setIsCopilotLspReady] = useState(false)
|
const [isCopilotLspReady, setIsCopilotLspReady] = useState(false)
|
||||||
|
|
||||||
const { auth } = useSettingsAuthContext()
|
const token = useToken()
|
||||||
const token = auth?.context.token
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
// So this is a bit weird, we need to initialize the lsp server and client.
|
// So this is a bit weird, we need to initialize the lsp server and client.
|
||||||
|
@ -89,6 +89,7 @@ import { Node } from 'wasm-lib/kcl/bindings/Node'
|
|||||||
import { promptToEditFlow } from 'lib/promptToEdit'
|
import { promptToEditFlow } from 'lib/promptToEdit'
|
||||||
import { kclEditorActor } from 'machines/kclEditorMachine'
|
import { kclEditorActor } from 'machines/kclEditorMachine'
|
||||||
import { commandBarActor } from 'machines/commandBarMachine'
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
import { useToken } from 'machines/appMachine'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -110,7 +111,6 @@ export const ModelingMachineProvider = ({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
auth,
|
|
||||||
settings: {
|
settings: {
|
||||||
context: {
|
context: {
|
||||||
app: { theme, enableSSAO, allowOrbitInSketchMode },
|
app: { theme, enableSSAO, allowOrbitInSketchMode },
|
||||||
@ -127,7 +127,7 @@ export const ModelingMachineProvider = ({
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { context, send: fileMachineSend } = useFileContext()
|
const { context, send: fileMachineSend } = useFileContext()
|
||||||
const { file } = useLoaderData() as IndexLoaderData
|
const { file } = useLoaderData() as IndexLoaderData
|
||||||
const token = auth?.context?.token
|
const token = useToken()
|
||||||
const streamRef = useRef<HTMLDivElement>(null)
|
const streamRef = useRef<HTMLDivElement>(null)
|
||||||
const persistedContext = useMemo(() => getPersistedContext(), [])
|
const persistedContext = useMemo(() => getPersistedContext(), [])
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import { useSelector } from '@xstate/react'
|
|||||||
import { copyFileShareLink } from 'lib/links'
|
import { copyFileShareLink } from 'lib/links'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { DEV } from 'env'
|
import { DEV } from 'env'
|
||||||
|
import { useToken } from 'machines/appMachine'
|
||||||
|
|
||||||
const ProjectSidebarMenu = ({
|
const ProjectSidebarMenu = ({
|
||||||
project,
|
project,
|
||||||
@ -103,7 +104,8 @@ function ProjectMenuPopover({
|
|||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const filePath = useAbsoluteFilePath()
|
const filePath = useAbsoluteFilePath()
|
||||||
const { settings, auth } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
|
const token = useToken()
|
||||||
const machineManager = useContext(MachineManagerContext)
|
const machineManager = useContext(MachineManagerContext)
|
||||||
const commands = useSelector(commandBarActor, commandsSelector)
|
const commands = useSelector(commandBarActor, commandsSelector)
|
||||||
|
|
||||||
@ -194,7 +196,7 @@ function ProjectMenuPopover({
|
|||||||
disabled: !DEV,
|
disabled: !DEV,
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
await copyFileShareLink({
|
await copyFileShareLink({
|
||||||
token: auth?.context.token || '',
|
token: token ?? '',
|
||||||
code: codeManager.code,
|
code: codeManager.code,
|
||||||
name: project?.name || '',
|
name: project?.name || '',
|
||||||
units: settings.context.modeling.defaultUnit.current,
|
units: settings.context.modeling.defaultUnit.current,
|
||||||
|
@ -8,10 +8,10 @@ import Tooltip from './Tooltip'
|
|||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { toSync } from 'lib/utils'
|
import { toSync } from 'lib/utils'
|
||||||
|
import { useToken } from 'machines/appMachine'
|
||||||
|
|
||||||
export const RefreshButton = ({ children }: React.PropsWithChildren) => {
|
export const RefreshButton = ({ children }: React.PropsWithChildren) => {
|
||||||
const { auth } = useSettingsAuthContext()
|
const token = useToken()
|
||||||
const token = auth?.context?.token
|
|
||||||
const coreDumpManager = useMemo(
|
const coreDumpManager = useMemo(
|
||||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||||
[]
|
[]
|
||||||
|
@ -2,10 +2,12 @@ import { useEffect, useState, createContext, ReactNode } from 'react'
|
|||||||
import { useNavigation, useLocation } from 'react-router-dom'
|
import { useNavigation, useLocation } from 'react-router-dom'
|
||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import { markOnce } from 'lib/performance'
|
import { markOnce } from 'lib/performance'
|
||||||
|
import { useAuthNavigation } from 'hooks/useAuthNavigation'
|
||||||
|
|
||||||
export const RouteProviderContext = createContext({})
|
export const RouteProviderContext = createContext({})
|
||||||
|
|
||||||
export function RouteProvider({ children }: { children: ReactNode }) {
|
export function RouteProvider({ children }: { children: ReactNode }) {
|
||||||
|
useAuthNavigation()
|
||||||
const [first, setFirstState] = useState(true)
|
const [first, setFirstState] = useState(true)
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
@ -2,10 +2,7 @@ import { trap } from 'lib/trap'
|
|||||||
import { useMachine, useSelector } from '@xstate/react'
|
import { useMachine, useSelector } from '@xstate/react'
|
||||||
import { useNavigate, useRouteLoaderData, useLocation } from 'react-router-dom'
|
import { useNavigate, useRouteLoaderData, useLocation } from 'react-router-dom'
|
||||||
import { PATHS, BROWSER_PATH } from 'lib/paths'
|
import { PATHS, BROWSER_PATH } from 'lib/paths'
|
||||||
import { authMachine, TOKEN_PERSIST_KEY } from '../machines/authMachine'
|
|
||||||
import withBaseUrl from '../lib/withBaseURL'
|
|
||||||
import React, { createContext, useEffect, useState } from 'react'
|
import React, { createContext, useEffect, useState } from 'react'
|
||||||
import useStateMachineCommands from '../hooks/useStateMachineCommands'
|
|
||||||
import { settingsMachine } from 'machines/settingsMachine'
|
import { settingsMachine } from 'machines/settingsMachine'
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
import {
|
import {
|
||||||
@ -16,7 +13,6 @@ import {
|
|||||||
} from 'lib/theme'
|
} from 'lib/theme'
|
||||||
import decamelize from 'decamelize'
|
import decamelize from 'decamelize'
|
||||||
import { Actor, AnyStateMachine, ContextFrom, Prop, StateFrom } from 'xstate'
|
import { Actor, AnyStateMachine, ContextFrom, Prop, StateFrom } from 'xstate'
|
||||||
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
|
|
||||||
import {
|
import {
|
||||||
kclManager,
|
kclManager,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
@ -50,7 +46,6 @@ type MachineContext<T extends AnyStateMachine> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SettingsAuthContextType = {
|
type SettingsAuthContextType = {
|
||||||
auth: MachineContext<typeof authMachine>
|
|
||||||
settings: MachineContext<typeof settingsMachine>
|
settings: MachineContext<typeof settingsMachine>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,40 +365,9 @@ export const SettingsAuthProviderBase = ({
|
|||||||
)
|
)
|
||||||
}, [settingsState.context.textEditor.blinkingCursor.current])
|
}, [settingsState.context.textEditor.blinkingCursor.current])
|
||||||
|
|
||||||
// Auth machine setup
|
|
||||||
const [authState, authSend, authActor] = useMachine(
|
|
||||||
authMachine.provide({
|
|
||||||
actions: {
|
|
||||||
goToSignInPage: () => {
|
|
||||||
navigate(PATHS.SIGN_IN)
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
logout()
|
|
||||||
},
|
|
||||||
goToIndexPage: () => {
|
|
||||||
if (location.pathname.includes(PATHS.SIGN_IN)) {
|
|
||||||
navigate(PATHS.INDEX)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
useStateMachineCommands({
|
|
||||||
machineId: 'auth',
|
|
||||||
state: authState,
|
|
||||||
send: authSend,
|
|
||||||
commandBarConfig: authCommandBarConfig,
|
|
||||||
actor: authActor,
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsAuthContext.Provider
|
<SettingsAuthContext.Provider
|
||||||
value={{
|
value={{
|
||||||
auth: {
|
|
||||||
state: authState,
|
|
||||||
context: authState.context,
|
|
||||||
send: authSend,
|
|
||||||
},
|
|
||||||
settings: {
|
settings: {
|
||||||
state: settingsState,
|
state: settingsState,
|
||||||
context: settingsState.context,
|
context: settingsState.context,
|
||||||
@ -417,12 +381,3 @@ export const SettingsAuthProviderBase = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default SettingsAuthProvider
|
export default SettingsAuthProvider
|
||||||
|
|
||||||
export async function logout() {
|
|
||||||
localStorage.removeItem(TOKEN_PERSIST_KEY)
|
|
||||||
if (isDesktop()) return Promise.resolve(null)
|
|
||||||
return fetch(withBaseUrl('/logout'), {
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'include',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -4,12 +4,12 @@ import { useLocation, useNavigate } from 'react-router-dom'
|
|||||||
import { Fragment, useMemo, useState } from 'react'
|
import { Fragment, useMemo, useState } from 'react'
|
||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
|
||||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||||
import Tooltip from './Tooltip'
|
import Tooltip from './Tooltip'
|
||||||
import usePlatform from 'hooks/usePlatform'
|
import usePlatform from 'hooks/usePlatform'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
|
import { authActor } from 'machines/appMachine'
|
||||||
|
|
||||||
type User = Models['User_type']
|
type User = Models['User_type']
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
const displayedName = getDisplayName(user)
|
const displayedName = getDisplayName(user)
|
||||||
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const send = useSettingsAuthContext()?.auth?.send
|
const send = authActor.send
|
||||||
|
|
||||||
// We filter this memoized list so that no orphan "break" elements are rendered.
|
// We filter this memoized list so that no orphan "break" elements are rendered.
|
||||||
const userMenuItems = useMemo<(ActionButtonProps | 'break')[]>(
|
const userMenuItems = useMemo<(ActionButtonProps | 'break')[]>(
|
||||||
|
29
src/hooks/useAuthNavigation.tsx
Normal file
29
src/hooks/useAuthNavigation.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { PATHS } from 'lib/paths'
|
||||||
|
import { useAuthState } from 'machines/appMachine'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple hook that listens to the auth state of the app and navigates
|
||||||
|
* accordingly.
|
||||||
|
*/
|
||||||
|
export function useAuthNavigation() {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
|
const authState = useAuthState()
|
||||||
|
|
||||||
|
// Subscribe to the auth state of the app and navigate accordingly.
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
authState.matches('loggedIn') &&
|
||||||
|
location.pathname.includes(PATHS.SIGN_IN)
|
||||||
|
) {
|
||||||
|
navigate(PATHS.INDEX)
|
||||||
|
} else if (
|
||||||
|
authState.matches('loggedOut') &&
|
||||||
|
!location.pathname.includes(PATHS.SIGN_IN)
|
||||||
|
) {
|
||||||
|
navigate(PATHS.SIGN_IN)
|
||||||
|
}
|
||||||
|
}, [authState])
|
||||||
|
}
|
@ -1,17 +1,14 @@
|
|||||||
import { StateMachineCommandSetConfig } from 'lib/commandTypes'
|
import { Command } from 'lib/commandTypes'
|
||||||
import { authMachine } from 'machines/authMachine'
|
import { authActor } from 'machines/appMachine'
|
||||||
|
import { ACTOR_IDS } from 'machines/machineConstants'
|
||||||
|
|
||||||
type AuthCommandSchema = {}
|
export const authCommands: Command[] = [
|
||||||
|
{
|
||||||
export const authCommandBarConfig: StateMachineCommandSetConfig<
|
groupId: ACTOR_IDS.AUTH,
|
||||||
typeof authMachine,
|
name: 'log-out',
|
||||||
AuthCommandSchema
|
displayName: 'Log out',
|
||||||
> = {
|
|
||||||
'Log in': {
|
|
||||||
hide: 'both',
|
|
||||||
},
|
|
||||||
'Log out': {
|
|
||||||
args: [],
|
|
||||||
icon: 'arrowLeft',
|
icon: 'arrowLeft',
|
||||||
|
needsReview: false,
|
||||||
|
onSubmit: () => authActor.send({ type: 'Log out' }),
|
||||||
},
|
},
|
||||||
}
|
]
|
||||||
|
30
src/machines/appMachine.ts
Normal file
30
src/machines/appMachine.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { ActorRefFrom, createActor, setup } from 'xstate'
|
||||||
|
import { authMachine } from './authMachine'
|
||||||
|
import { useSelector } from '@xstate/react'
|
||||||
|
import { ACTOR_IDS } from './machineConstants'
|
||||||
|
|
||||||
|
const appMachine = setup({
|
||||||
|
actors: {
|
||||||
|
[ACTOR_IDS.AUTH]: authMachine,
|
||||||
|
},
|
||||||
|
}).createMachine({
|
||||||
|
/** @xstate-layout N4IgpgJg5mDOIC5gF8A0IB2B7CdGgAoBbAQwGMALASwzAEp8QAHLWKgFyqw0YA9EAjACZ0AT0FDkU5EA */
|
||||||
|
id: 'modeling-app',
|
||||||
|
invoke: [
|
||||||
|
{
|
||||||
|
src: ACTOR_IDS.AUTH,
|
||||||
|
systemId: ACTOR_IDS.AUTH,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
export const appActor = createActor(appMachine).start()
|
||||||
|
|
||||||
|
export const authActor = appActor.system.get(ACTOR_IDS.AUTH) as ActorRefFrom<
|
||||||
|
typeof authMachine
|
||||||
|
>
|
||||||
|
export const useAuthState = () => useSelector(authActor, (state) => state)
|
||||||
|
export const useToken = () =>
|
||||||
|
useSelector(authActor, (state) => state.context.token)
|
||||||
|
export const useUser = () =>
|
||||||
|
useSelector(authActor, (state) => state.context.user)
|
@ -15,6 +15,8 @@ import {
|
|||||||
} from 'lib/desktop'
|
} from 'lib/desktop'
|
||||||
import { COOKIE_NAME } from 'lib/constants'
|
import { COOKIE_NAME } from 'lib/constants'
|
||||||
import { markOnce } from 'lib/performance'
|
import { markOnce } from 'lib/performance'
|
||||||
|
import { ACTOR_IDS } from './machineConstants'
|
||||||
|
import withBaseUrl from '../lib/withBaseURL'
|
||||||
|
|
||||||
const SKIP_AUTH = VITE_KC_SKIP_AUTH === 'true' && DEV
|
const SKIP_AUTH = VITE_KC_SKIP_AUTH === 'true' && DEV
|
||||||
|
|
||||||
@ -50,7 +52,7 @@ export type Events =
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY'
|
export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY'
|
||||||
const persistedToken =
|
export const persistedToken =
|
||||||
VITE_KC_DEV_TOKEN ||
|
VITE_KC_DEV_TOKEN ||
|
||||||
getCookie(COOKIE_NAME) ||
|
getCookie(COOKIE_NAME) ||
|
||||||
localStorage?.getItem(TOKEN_PERSIST_KEY) ||
|
localStorage?.getItem(TOKEN_PERSIST_KEY) ||
|
||||||
@ -69,18 +71,17 @@ export const authMachine = setup({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
|
||||||
goToIndexPage: () => {},
|
|
||||||
goToSignInPage: () => {},
|
|
||||||
},
|
|
||||||
actors: {
|
actors: {
|
||||||
getUser: fromPromise(({ input }: { input: { token?: string } }) =>
|
getUser: fromPromise(({ input }: { input: { token?: string } }) =>
|
||||||
getUser(input)
|
getUser(input)
|
||||||
),
|
),
|
||||||
|
logout: fromPromise(async () =>
|
||||||
|
isDesktop() ? writeTokenFile('') : logout()
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}).createMachine({
|
}).createMachine({
|
||||||
/** @xstate-layout N4IgpgJg5mDOIC5QEECuAXAFgOgMabFwGsBJAMwBkB7KGCEgOwGIIqGxsBLBgNyqI75CRALQAbGnRHcA2gAYAuolAAHKrE7pObZSAAeiAIwAWQ9gBspuQCYAnAGYAHPYCsx+4ccAaEAE9E1q7YcoZyxrYR1m7mcrYAvnE+aFh4BMTk1LSQjExgAE55VHnYKmIAhuhkRQC2qcLikpDSDPJKSCBqGlo67QYI9gDs5tge5o6h5vau7oY+-v3mA9jWco4u5iu21ua2YcYJSRg4Eln0zJkABFQYrbqdmtoMun2GA7YjxuPmLqvGNh5zRCfJaOcyLUzuAYuFyGcwHEDJY6NCAAeQwTEuskUd3UDx6oD6Im2wUcAzkMJ2cjBxlMgIWLmwZLWljecjJTjh8IYVAgcF0iJxXUez0QIgGxhJZIpu2ptL8AWwtje1nCW2iq1shns8MRdXSlGRjEFeKevUQjkcy3sqwGHimbg83nlCF22GMytVUWMMUc8USCKO2BOdCN7Xu3VNBKMKsVFp2hm2vu+1id83slkVrgTxhcW0pNJ1geDkDR6GNEZFCAT1kZZLk9cMLltb0WdPMjewjjC1mzOZCtk5CSAA */
|
/** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOhzEwGsBJAMwBkB7KGCa-AYgkcJIIDdGlMGWwVKAWgA2zVhIIBtAAwBdRKAAOjWLgAuuHupAAPRAGYArAEYSADgu2AnGYBMLpVYBsZz7YA0IACeiG6OJM62tmZKLgDsno5KtvEAvikBaFh4hKTkVHRMLJDsHGAATmWMZSQaUui6tFWoouLSspDy+MpqSCBaOvqGvaYIljb2Tq7uXj7+QYgALFYW4clWy1ZmVgsWsZtpGRg4BMQkMkVsnIUABIwArrrdRv16BvhGI74LJBYW7o5WKJmKILObBUZeEgJP4LTxKMwIhZmBYLA4gTLHHJnWQEKAAeQeXB4IgEQhEGOyp3OUFxBN0CFJmHqb26T16L0G72GiCsSg8PyszkBCViTiUjgC4Jcnhc4SUsQcvgsoL2VjRFJOpGptMJ5Uq1Vq9UaZWaGqx2vw+IeDPwgiZnNZqme2leQ1An1s31+-0BCJBYJCLm+lk8CRl9hRyos6qOlK17QgdI4N0UTvZLs5Hx58NsJARuys0tDSl+AYQthsgNi0TMqt2LjVaPwjAgcCMZuIzoGbyzCAknkliH7Maympa+QYCfYXddXPdixcg4QvKUdk2u2iLkcsXhCRHmKpU7nfQzPe5CAsMpIXi8MvFKM8VliS5c1jzj53W3isNFqPS6NjMcLStXQZ0zc8ohsJI-kcFxXEcR9HAWF9gTzDxbCUXxAQWEsdn3ONsQuOkwLPedl22MIzFg3YP1gl9PG+bYvGsSxlUcRJozSFIgA */
|
||||||
id: 'Auth',
|
id: ACTOR_IDS.AUTH,
|
||||||
initial: 'checkIfLoggedIn',
|
initial: 'checkIfLoggedIn',
|
||||||
context: {
|
context: {
|
||||||
token: persistedToken,
|
token: persistedToken,
|
||||||
@ -112,19 +113,30 @@ export const authMachine = setup({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
loggedIn: {
|
loggedIn: {
|
||||||
entry: ['goToIndexPage'],
|
|
||||||
on: {
|
on: {
|
||||||
'Log out': {
|
'Log out': {
|
||||||
target: 'loggedOut',
|
target: 'loggingOut',
|
||||||
actions: () => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
if (isDesktop()) writeTokenFile('')
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
loggingOut: {
|
||||||
|
invoke: {
|
||||||
|
src: 'logout',
|
||||||
|
onDone: 'loggedOut',
|
||||||
|
onError: {
|
||||||
|
target: 'loggedIn',
|
||||||
|
actions: [
|
||||||
|
({ event }) => {
|
||||||
|
console.error(
|
||||||
|
'Error while logging out',
|
||||||
|
'error' in event ? `: ${event.error}` : ''
|
||||||
|
)
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
loggedOut: {
|
loggedOut: {
|
||||||
entry: ['goToSignInPage'],
|
|
||||||
on: {
|
on: {
|
||||||
'Log in': {
|
'Log in': {
|
||||||
target: 'checkIfLoggedIn',
|
target: 'checkIfLoggedIn',
|
||||||
@ -235,3 +247,12 @@ async function getAndSyncStoredToken(input: {
|
|||||||
localStorage.setItem(TOKEN_PERSIST_KEY, fileToken)
|
localStorage.setItem(TOKEN_PERSIST_KEY, fileToken)
|
||||||
return fileToken
|
return fileToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function logout() {
|
||||||
|
localStorage.removeItem(TOKEN_PERSIST_KEY)
|
||||||
|
if (isDesktop()) return Promise.resolve(null)
|
||||||
|
return fetch(withBaseUrl('/logout'), {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
|
|||||||
import { MachineManager } from 'components/MachineManagerProvider'
|
import { MachineManager } from 'components/MachineManagerProvider'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { useSelector } from '@xstate/react'
|
import { useSelector } from '@xstate/react'
|
||||||
|
import { authCommands } from 'lib/commandBarConfigs/authCommandConfig'
|
||||||
|
|
||||||
export type CommandBarContext = {
|
export type CommandBarContext = {
|
||||||
commands: Command[]
|
commands: Command[]
|
||||||
@ -80,6 +81,7 @@ export type CommandBarMachineEvent =
|
|||||||
export const commandBarMachine = setup({
|
export const commandBarMachine = setup({
|
||||||
types: {
|
types: {
|
||||||
context: {} as CommandBarContext,
|
context: {} as CommandBarContext,
|
||||||
|
input: {} as { commands: Command[] },
|
||||||
events: {} as CommandBarMachineEvent,
|
events: {} as CommandBarMachineEvent,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
@ -409,8 +411,8 @@ export const commandBarMachine = setup({
|
|||||||
},
|
},
|
||||||
}).createMachine({
|
}).createMachine({
|
||||||
/** @xstate-layout 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 */
|
/** @xstate-layout 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 */
|
||||||
context: {
|
context: ({ input }) => ({
|
||||||
commands: [],
|
commands: input.commands || [],
|
||||||
selectedCommand: undefined,
|
selectedCommand: undefined,
|
||||||
currentArgument: undefined,
|
currentArgument: undefined,
|
||||||
selectionRanges: {
|
selectionRanges: {
|
||||||
@ -425,7 +427,7 @@ export const commandBarMachine = setup({
|
|||||||
setCurrentMachine: () => {},
|
setCurrentMachine: () => {},
|
||||||
noMachinesReason: () => undefined,
|
noMachinesReason: () => undefined,
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
id: 'Command Bar',
|
id: 'Command Bar',
|
||||||
initial: 'Closed',
|
initial: 'Closed',
|
||||||
states: {
|
states: {
|
||||||
@ -631,7 +633,11 @@ function sortCommands(a: Command, b: Command) {
|
|||||||
return a.name.localeCompare(b.name)
|
return a.name.localeCompare(b.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const commandBarActor = createActor(commandBarMachine).start()
|
export const commandBarActor = createActor(commandBarMachine, {
|
||||||
|
input: {
|
||||||
|
commands: [...authCommands],
|
||||||
|
},
|
||||||
|
}).start()
|
||||||
|
|
||||||
/** Basic state snapshot selector */
|
/** Basic state snapshot selector */
|
||||||
const cmdBarStateSelector = (state: SnapshotFrom<typeof commandBarActor>) =>
|
const cmdBarStateSelector = (state: SnapshotFrom<typeof commandBarActor>) =>
|
||||||
|
3
src/machines/machineConstants.ts
Normal file
3
src/machines/machineConstants.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const ACTOR_IDS = {
|
||||||
|
AUTH: 'auth',
|
||||||
|
}
|
@ -1,15 +1,14 @@
|
|||||||
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useUser } from 'machines/appMachine'
|
||||||
|
|
||||||
export default function UserMenu() {
|
export default function UserMenu() {
|
||||||
const { auth } = useSettingsAuthContext()
|
const user = useUser()
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
const next = useNextClick(onboardingPaths.PROJECT_MENU)
|
const next = useNextClick(onboardingPaths.PROJECT_MENU)
|
||||||
const [avatarErrored, setAvatarErrored] = useState(false)
|
const [avatarErrored, setAvatarErrored] = useState(false)
|
||||||
|
|
||||||
const user = auth?.context?.user
|
|
||||||
const errorOrNoImage = !user?.image || avatarErrored
|
const errorOrNoImage = !user?.image || avatarErrored
|
||||||
const buttonDescription = errorOrNoImage ? 'the menu button' : 'your avatar'
|
const buttonDescription = errorOrNoImage ? 'the menu button' : 'your avatar'
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
|||||||
import { toSync } from 'lib/utils'
|
import { toSync } from 'lib/utils'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
|
import { authActor } from 'machines/appMachine'
|
||||||
|
|
||||||
const subtleBorder =
|
const subtleBorder =
|
||||||
'border border-solid border-chalkboard-30 dark:border-chalkboard-80'
|
'border border-solid border-chalkboard-30 dark:border-chalkboard-80'
|
||||||
@ -22,7 +23,6 @@ const cardArea = `${subtleBorder} rounded-lg px-6 py-3 text-chalkboard-70 dark:t
|
|||||||
const SignIn = () => {
|
const SignIn = () => {
|
||||||
const [userCode, setUserCode] = useState('')
|
const [userCode, setUserCode] = useState('')
|
||||||
const {
|
const {
|
||||||
auth: { send },
|
|
||||||
settings: {
|
settings: {
|
||||||
state: {
|
state: {
|
||||||
context: {
|
context: {
|
||||||
@ -70,7 +70,7 @@ const SignIn = () => {
|
|||||||
toast.error('Error while trying to log in')
|
toast.error('Error while trying to log in')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
send({ type: 'Log in', token })
|
authActor.send({ type: 'Log in', token })
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
Reference in New Issue
Block a user