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 { maybeWriteToDisk } from 'lib/telemetry'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
maybeWriteToDisk()
|
||||
.then(() => {})
|
||||
.catch(() => {})
|
||||
@ -60,8 +61,8 @@ export function App() {
|
||||
|
||||
useHotKeyListener()
|
||||
|
||||
const { auth, settings } = useSettingsAuthContext()
|
||||
const token = auth?.context?.token
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const token = useToken()
|
||||
|
||||
const coreDumpManager = useMemo(
|
||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { useAuthState } from 'machines/appMachine'
|
||||
import Loading from './components/Loading'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
|
||||
// Wrapper around protected routes, used in src/Router.tsx
|
||||
export const Auth = ({ children }: React.PropsWithChildren) => {
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const isLoggingIn = auth?.state.matches('checkIfLoggedIn')
|
||||
const authState = useAuthState()
|
||||
const isLoggingIn = authState.matches('checkIfLoggedIn')
|
||||
|
||||
return isLoggingIn ? (
|
||||
<Loading>
|
||||
|
@ -37,7 +37,6 @@ import { KclContextProvider } from 'lang/KclProvider'
|
||||
import { ASK_TO_OPEN_QUERY_PARAM, BROWSER_PROJECT_NAME } from 'lib/constants'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import { codeManager, engineCommandManager } from 'lib/singletons'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||
import toast from 'react-hot-toast'
|
||||
import { coreDump } from 'lang/wasm'
|
||||
@ -47,6 +46,7 @@ import { reportRejection } from 'lib/trap'
|
||||
import { RouteProvider } from 'components/RouteProvider'
|
||||
import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
|
||||
import { OpenInDesktopAppHandler } from 'components/OpenInDesktopAppHandler'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
|
||||
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
|
||||
|
||||
@ -203,8 +203,7 @@ export const Router = () => {
|
||||
}
|
||||
|
||||
function CoreDump() {
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const token = auth?.context?.token
|
||||
const token = useToken()
|
||||
const coreDumpManager = useMemo(
|
||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||
[]
|
||||
|
@ -2,11 +2,11 @@ import { Toolbar } from '../Toolbar'
|
||||
import UserSidebarMenu from 'components/UserSidebarMenu'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import styles from './AppHeader.module.css'
|
||||
import { RefreshButton } from 'components/RefreshButton'
|
||||
import { CommandBarOpenButton } from './CommandBarOpenButton'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { useUser } from 'machines/appMachine'
|
||||
|
||||
interface AppHeaderProps extends React.PropsWithChildren {
|
||||
showToolbar?: boolean
|
||||
@ -24,8 +24,7 @@ export const AppHeader = ({
|
||||
style,
|
||||
enableMenu = false,
|
||||
}: AppHeaderProps) => {
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const user = auth?.context?.user
|
||||
const user = useUser()
|
||||
|
||||
return (
|
||||
<header
|
||||
|
@ -30,6 +30,7 @@ import {
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { markOnce } from 'lib/performance'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -47,7 +48,8 @@ export const FileMachineProvider = ({
|
||||
children: React.ReactNode
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
const { settings, auth } = useSettingsAuthContext()
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const token = useToken()
|
||||
const projectData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
||||
const { project, file } = projectData
|
||||
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
|
||||
@ -297,7 +299,7 @@ export const FileMachineProvider = ({
|
||||
const kclCommandMemo = useMemo(
|
||||
() =>
|
||||
kclCommands({
|
||||
authToken: auth?.context?.token ?? '',
|
||||
authToken: token ?? '',
|
||||
projectData,
|
||||
settings: {
|
||||
defaultUnit: settings?.context?.modeling.defaultUnit.current ?? 'mm',
|
||||
|
@ -27,6 +27,7 @@ import { PROJECT_ENTRYPOINT } from 'lib/constants'
|
||||
import { err } from 'lib/trap'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { codeManager } from 'lib/singletons'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
|
||||
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
||||
return []
|
||||
@ -69,8 +70,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [isKclLspReady, setIsKclLspReady] = useState(false)
|
||||
const [isCopilotLspReady, setIsCopilotLspReady] = useState(false)
|
||||
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const token = auth?.context.token
|
||||
const token = useToken()
|
||||
const navigate = useNavigate()
|
||||
|
||||
// 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 { kclEditorActor } from 'machines/kclEditorMachine'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -110,7 +111,6 @@ export const ModelingMachineProvider = ({
|
||||
children: React.ReactNode
|
||||
}) => {
|
||||
const {
|
||||
auth,
|
||||
settings: {
|
||||
context: {
|
||||
app: { theme, enableSSAO, allowOrbitInSketchMode },
|
||||
@ -127,7 +127,7 @@ export const ModelingMachineProvider = ({
|
||||
const navigate = useNavigate()
|
||||
const { context, send: fileMachineSend } = useFileContext()
|
||||
const { file } = useLoaderData() as IndexLoaderData
|
||||
const token = auth?.context?.token
|
||||
const token = useToken()
|
||||
const streamRef = useRef<HTMLDivElement>(null)
|
||||
const persistedContext = useMemo(() => getPersistedContext(), [])
|
||||
|
||||
|
@ -20,6 +20,7 @@ import { useSelector } from '@xstate/react'
|
||||
import { copyFileShareLink } from 'lib/links'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { DEV } from 'env'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
|
||||
const ProjectSidebarMenu = ({
|
||||
project,
|
||||
@ -103,7 +104,8 @@ function ProjectMenuPopover({
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const filePath = useAbsoluteFilePath()
|
||||
const { settings, auth } = useSettingsAuthContext()
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const token = useToken()
|
||||
const machineManager = useContext(MachineManagerContext)
|
||||
const commands = useSelector(commandBarActor, commandsSelector)
|
||||
|
||||
@ -194,7 +196,7 @@ function ProjectMenuPopover({
|
||||
disabled: !DEV,
|
||||
onClick: async () => {
|
||||
await copyFileShareLink({
|
||||
token: auth?.context.token || '',
|
||||
token: token ?? '',
|
||||
code: codeManager.code,
|
||||
name: project?.name || '',
|
||||
units: settings.context.modeling.defaultUnit.current,
|
||||
|
@ -8,10 +8,10 @@ import Tooltip from './Tooltip'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { toSync } from 'lib/utils'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
|
||||
export const RefreshButton = ({ children }: React.PropsWithChildren) => {
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const token = auth?.context?.token
|
||||
const token = useToken()
|
||||
const coreDumpManager = useMemo(
|
||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||
[]
|
||||
|
@ -2,10 +2,12 @@ import { useEffect, useState, createContext, ReactNode } from 'react'
|
||||
import { useNavigation, useLocation } from 'react-router-dom'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import { markOnce } from 'lib/performance'
|
||||
import { useAuthNavigation } from 'hooks/useAuthNavigation'
|
||||
|
||||
export const RouteProviderContext = createContext({})
|
||||
|
||||
export function RouteProvider({ children }: { children: ReactNode }) {
|
||||
useAuthNavigation()
|
||||
const [first, setFirstState] = useState(true)
|
||||
const navigation = useNavigation()
|
||||
const location = useLocation()
|
||||
|
@ -2,10 +2,7 @@ import { trap } from 'lib/trap'
|
||||
import { useMachine, useSelector } from '@xstate/react'
|
||||
import { useNavigate, useRouteLoaderData, useLocation } from 'react-router-dom'
|
||||
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 useStateMachineCommands from '../hooks/useStateMachineCommands'
|
||||
import { settingsMachine } from 'machines/settingsMachine'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import {
|
||||
@ -16,7 +13,6 @@ import {
|
||||
} from 'lib/theme'
|
||||
import decamelize from 'decamelize'
|
||||
import { Actor, AnyStateMachine, ContextFrom, Prop, StateFrom } from 'xstate'
|
||||
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
|
||||
import {
|
||||
kclManager,
|
||||
sceneInfra,
|
||||
@ -50,7 +46,6 @@ type MachineContext<T extends AnyStateMachine> = {
|
||||
}
|
||||
|
||||
type SettingsAuthContextType = {
|
||||
auth: MachineContext<typeof authMachine>
|
||||
settings: MachineContext<typeof settingsMachine>
|
||||
}
|
||||
|
||||
@ -370,40 +365,9 @@ export const SettingsAuthProviderBase = ({
|
||||
)
|
||||
}, [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 (
|
||||
<SettingsAuthContext.Provider
|
||||
value={{
|
||||
auth: {
|
||||
state: authState,
|
||||
context: authState.context,
|
||||
send: authSend,
|
||||
},
|
||||
settings: {
|
||||
state: settingsState,
|
||||
context: settingsState.context,
|
||||
@ -417,12 +381,3 @@ export const SettingsAuthProviderBase = ({
|
||||
}
|
||||
|
||||
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 { PATHS } from 'lib/paths'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
import Tooltip from './Tooltip'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { authActor } from 'machines/appMachine'
|
||||
|
||||
type User = Models['User_type']
|
||||
|
||||
@ -20,7 +20,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||
const displayedName = getDisplayName(user)
|
||||
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
||||
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.
|
||||
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 { authMachine } from 'machines/authMachine'
|
||||
import { Command } from 'lib/commandTypes'
|
||||
import { authActor } from 'machines/appMachine'
|
||||
import { ACTOR_IDS } from 'machines/machineConstants'
|
||||
|
||||
type AuthCommandSchema = {}
|
||||
|
||||
export const authCommandBarConfig: StateMachineCommandSetConfig<
|
||||
typeof authMachine,
|
||||
AuthCommandSchema
|
||||
> = {
|
||||
'Log in': {
|
||||
hide: 'both',
|
||||
},
|
||||
'Log out': {
|
||||
args: [],
|
||||
export const authCommands: Command[] = [
|
||||
{
|
||||
groupId: ACTOR_IDS.AUTH,
|
||||
name: 'log-out',
|
||||
displayName: 'Log out',
|
||||
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'
|
||||
import { COOKIE_NAME } from 'lib/constants'
|
||||
import { markOnce } from 'lib/performance'
|
||||
import { ACTOR_IDS } from './machineConstants'
|
||||
import withBaseUrl from '../lib/withBaseURL'
|
||||
|
||||
const SKIP_AUTH = VITE_KC_SKIP_AUTH === 'true' && DEV
|
||||
|
||||
@ -50,7 +52,7 @@ export type Events =
|
||||
}
|
||||
|
||||
export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY'
|
||||
const persistedToken =
|
||||
export const persistedToken =
|
||||
VITE_KC_DEV_TOKEN ||
|
||||
getCookie(COOKIE_NAME) ||
|
||||
localStorage?.getItem(TOKEN_PERSIST_KEY) ||
|
||||
@ -69,18 +71,17 @@ export const authMachine = setup({
|
||||
}
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
goToIndexPage: () => {},
|
||||
goToSignInPage: () => {},
|
||||
},
|
||||
actors: {
|
||||
getUser: fromPromise(({ input }: { input: { token?: string } }) =>
|
||||
getUser(input)
|
||||
),
|
||||
logout: fromPromise(async () =>
|
||||
isDesktop() ? writeTokenFile('') : logout()
|
||||
),
|
||||
},
|
||||
}).createMachine({
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QEECuAXAFgOgMabFwGsBJAMwBkB7KGCEgOwGIIqGxsBLBgNyqI75CRALQAbGnRHcA2gAYAuolAAHKrE7pObZSAAeiAIwAWQ9gBspuQCYAnAGYAHPYCsx+4ccAaEAE9E1q7YcoZyxrYR1m7mcrYAvnE+aFh4BMTk1LSQjExgAE55VHnYKmIAhuhkRQC2qcLikpDSDPJKSCBqGlo67QYI9gDs5tge5o6h5vau7oY+-v3mA9jWco4u5iu21ua2YcYJSRg4Eln0zJkABFQYrbqdmtoMun2GA7YjxuPmLqvGNh5zRCfJaOcyLUzuAYuFyGcwHEDJY6NCAAeQwTEuskUd3UDx6oD6Im2wUcAzkMJ2cjBxlMgIWLmwZLWljecjJTjh8IYVAgcF0iJxXUez0QIgGxhJZIpu2ptL8AWwtje1nCW2iq1shns8MRdXSlGRjEFeKevUQjkcy3sqwGHimbg83nlCF22GMytVUWMMUc8USCKO2BOdCN7Xu3VNBKMKsVFp2hm2vu+1id83slkVrgTxhcW0pNJ1geDkDR6GNEZFCAT1kZZLk9cMLltb0WdPMjewjjC1mzOZCtk5CSAA */
|
||||
id: 'Auth',
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOhzEwGsBJAMwBkB7KGCa-AYgkcJIIDdGlMGWwVKAWgA2zVhIIBtAAwBdRKAAOjWLgAuuHupAAPRAGYArAEYSADgu2AnGYBMLpVYBsZz7YA0IACeiG6OJM62tmZKLgDsno5KtvEAvikBaFh4hKTkVHRMLJDsHGAATmWMZSQaUui6tFWoouLSspDy+MpqSCBaOvqGvaYIljb2Tq7uXj7+QYgALFYW4clWy1ZmVgsWsZtpGRg4BMQkMkVsnIUABIwArrrdRv16BvhGI74LJBYW7o5WKJmKILObBUZeEgJP4LTxKMwIhZmBYLA4gTLHHJnWQEKAAeQeXB4IgEQhEGOyp3OUFxBN0CFJmHqb26T16L0G72GiCsSg8PyszkBCViTiUjgC4Jcnhc4SUsQcvgsoL2VjRFJOpGptMJ5Uq1Vq9UaZWaGqx2vw+IeDPwgiZnNZqme2leQ1An1s31+-0BCJBYJCLm+lk8CRl9hRyos6qOlK17QgdI4N0UTvZLs5Hx58NsJARuys0tDSl+AYQthsgNi0TMqt2LjVaPwjAgcCMZuIzoGbyzCAknkliH7Maympa+QYCfYXddXPdixcg4QvKUdk2u2iLkcsXhCRHmKpU7nfQzPe5CAsMpIXi8MvFKM8VliS5c1jzj53W3isNFqPS6NjMcLStXQZ0zc8ohsJI-kcFxXEcR9HAWF9gTzDxbCUXxAQWEsdn3ONsQuOkwLPedl22MIzFg3YP1gl9PG+bYvGsSxlUcRJozSFIgA */
|
||||
id: ACTOR_IDS.AUTH,
|
||||
initial: 'checkIfLoggedIn',
|
||||
context: {
|
||||
token: persistedToken,
|
||||
@ -112,19 +113,30 @@ export const authMachine = setup({
|
||||
},
|
||||
},
|
||||
loggedIn: {
|
||||
entry: ['goToIndexPage'],
|
||||
on: {
|
||||
'Log out': {
|
||||
target: 'loggedOut',
|
||||
actions: () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
if (isDesktop()) writeTokenFile('')
|
||||
},
|
||||
target: 'loggingOut',
|
||||
},
|
||||
},
|
||||
},
|
||||
loggingOut: {
|
||||
invoke: {
|
||||
src: 'logout',
|
||||
onDone: 'loggedOut',
|
||||
onError: {
|
||||
target: 'loggedIn',
|
||||
actions: [
|
||||
({ event }) => {
|
||||
console.error(
|
||||
'Error while logging out',
|
||||
'error' in event ? `: ${event.error}` : ''
|
||||
)
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
loggedOut: {
|
||||
entry: ['goToSignInPage'],
|
||||
on: {
|
||||
'Log in': {
|
||||
target: 'checkIfLoggedIn',
|
||||
@ -235,3 +247,12 @@ async function getAndSyncStoredToken(input: {
|
||||
localStorage.setItem(TOKEN_PERSIST_KEY, 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 toast from 'react-hot-toast'
|
||||
import { useSelector } from '@xstate/react'
|
||||
import { authCommands } from 'lib/commandBarConfigs/authCommandConfig'
|
||||
|
||||
export type CommandBarContext = {
|
||||
commands: Command[]
|
||||
@ -80,6 +81,7 @@ export type CommandBarMachineEvent =
|
||||
export const commandBarMachine = setup({
|
||||
types: {
|
||||
context: {} as CommandBarContext,
|
||||
input: {} as { commands: Command[] },
|
||||
events: {} as CommandBarMachineEvent,
|
||||
},
|
||||
actions: {
|
||||
@ -409,8 +411,8 @@ export const commandBarMachine = setup({
|
||||
},
|
||||
}).createMachine({
|
||||
/** @xstate-layout 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 */
|
||||
context: {
|
||||
commands: [],
|
||||
context: ({ input }) => ({
|
||||
commands: input.commands || [],
|
||||
selectedCommand: undefined,
|
||||
currentArgument: undefined,
|
||||
selectionRanges: {
|
||||
@ -425,7 +427,7 @@ export const commandBarMachine = setup({
|
||||
setCurrentMachine: () => {},
|
||||
noMachinesReason: () => undefined,
|
||||
},
|
||||
},
|
||||
}),
|
||||
id: 'Command Bar',
|
||||
initial: 'Closed',
|
||||
states: {
|
||||
@ -631,7 +633,11 @@ function sortCommands(a: Command, b: Command) {
|
||||
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 */
|
||||
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 { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useUser } from 'machines/appMachine'
|
||||
|
||||
export default function UserMenu() {
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const user = useUser()
|
||||
const dismiss = useDismiss()
|
||||
const next = useNextClick(onboardingPaths.PROJECT_MENU)
|
||||
const [avatarErrored, setAvatarErrored] = useState(false)
|
||||
|
||||
const user = auth?.context?.user
|
||||
const errorOrNoImage = !user?.image || avatarErrored
|
||||
const buttonDescription = errorOrNoImage ? 'the menu button' : 'your avatar'
|
||||
|
||||
|
@ -14,6 +14,7 @@ import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
import { toSync } from 'lib/utils'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import toast from 'react-hot-toast'
|
||||
import { authActor } from 'machines/appMachine'
|
||||
|
||||
const subtleBorder =
|
||||
'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 [userCode, setUserCode] = useState('')
|
||||
const {
|
||||
auth: { send },
|
||||
settings: {
|
||||
state: {
|
||||
context: {
|
||||
@ -70,7 +70,7 @@ const SignIn = () => {
|
||||
toast.error('Error while trying to log in')
|
||||
return
|
||||
}
|
||||
send({ type: 'Log in', token })
|
||||
authActor.send({ type: 'Log in', token })
|
||||
}
|
||||
|
||||
return (
|
||||
|
Reference in New Issue
Block a user