From b0426e3f94e365d1678660c43f96723a8e7a7975 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Fri, 31 Jan 2025 14:47:08 -0500 Subject: [PATCH] Refactor: separate authMachine from React (#5110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 8dc50b6a26e435e237e600c7457668761a577d65. --------- Co-authored-by: github-actions[bot] --- src/App.tsx | 5 +- src/Auth.tsx | 6 +-- src/Router.tsx | 5 +- src/components/AppHeader.tsx | 5 +- src/components/FileMachineProvider.tsx | 6 ++- src/components/LspProvider.tsx | 4 +- src/components/ModelingMachineProvider.tsx | 4 +- src/components/ProjectSidebarMenu.tsx | 6 ++- src/components/RefreshButton.tsx | 4 +- src/components/RouteProvider.tsx | 2 + src/components/SettingsAuthProvider.tsx | 45 ----------------- src/components/UserSidebarMenu.tsx | 4 +- src/hooks/useAuthNavigation.tsx | 29 +++++++++++ .../commandBarConfigs/authCommandConfig.ts | 25 +++++----- src/machines/appMachine.ts | 30 ++++++++++++ src/machines/authMachine.ts | 49 +++++++++++++------ src/machines/commandBarMachine.ts | 14 ++++-- src/machines/machineConstants.ts | 3 ++ src/routes/Onboarding/UserMenu.tsx | 5 +- src/routes/SignIn.tsx | 4 +- 20 files changed, 150 insertions(+), 105 deletions(-) create mode 100644 src/hooks/useAuthNavigation.tsx create mode 100644 src/machines/appMachine.ts create mode 100644 src/machines/machineConstants.ts diff --git a/src/App.tsx b/src/App.tsx index dbf34ce13..fc58e6df8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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), diff --git a/src/Auth.tsx b/src/Auth.tsx index 19260e605..7281471c2 100644 --- a/src/Auth.tsx +++ b/src/Auth.tsx @@ -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 ? ( diff --git a/src/Router.tsx b/src/Router.tsx index d4d3e44f5..7f78328b5 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -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), [] diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx index 7465625d7..58dd37b20 100644 --- a/src/components/AppHeader.tsx +++ b/src/components/AppHeader.tsx @@ -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 (
= { state: StateFrom @@ -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( @@ -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', diff --git a/src/components/LspProvider.tsx b/src/components/LspProvider.tsx index dd5b2d5b9..33ec402b3 100644 --- a/src/components/LspProvider.tsx +++ b/src/components/LspProvider.tsx @@ -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. diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx index dbcfd8b71..a7fb59692 100644 --- a/src/components/ModelingMachineProvider.tsx +++ b/src/components/ModelingMachineProvider.tsx @@ -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 = { state: StateFrom @@ -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(null) const persistedContext = useMemo(() => getPersistedContext(), []) diff --git a/src/components/ProjectSidebarMenu.tsx b/src/components/ProjectSidebarMenu.tsx index 9ae704f1e..663026650 100644 --- a/src/components/ProjectSidebarMenu.tsx +++ b/src/components/ProjectSidebarMenu.tsx @@ -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, diff --git a/src/components/RefreshButton.tsx b/src/components/RefreshButton.tsx index adf82ef2a..c6cfeaf13 100644 --- a/src/components/RefreshButton.tsx +++ b/src/components/RefreshButton.tsx @@ -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), [] diff --git a/src/components/RouteProvider.tsx b/src/components/RouteProvider.tsx index f42ba8339..f50d4996c 100644 --- a/src/components/RouteProvider.tsx +++ b/src/components/RouteProvider.tsx @@ -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() diff --git a/src/components/SettingsAuthProvider.tsx b/src/components/SettingsAuthProvider.tsx index 727877838..8dcae757c 100644 --- a/src/components/SettingsAuthProvider.tsx +++ b/src/components/SettingsAuthProvider.tsx @@ -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 = { } type SettingsAuthContextType = { - auth: MachineContext settings: MachineContext } @@ -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 ( { 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')[]>( diff --git a/src/hooks/useAuthNavigation.tsx b/src/hooks/useAuthNavigation.tsx new file mode 100644 index 000000000..b58bc99c0 --- /dev/null +++ b/src/hooks/useAuthNavigation.tsx @@ -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]) +} diff --git a/src/lib/commandBarConfigs/authCommandConfig.ts b/src/lib/commandBarConfigs/authCommandConfig.ts index 5a2cb1921..1f77434ae 100644 --- a/src/lib/commandBarConfigs/authCommandConfig.ts +++ b/src/lib/commandBarConfigs/authCommandConfig.ts @@ -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' }), }, -} +] diff --git a/src/machines/appMachine.ts b/src/machines/appMachine.ts new file mode 100644 index 000000000..fd2e757db --- /dev/null +++ b/src/machines/appMachine.ts @@ -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) diff --git a/src/machines/authMachine.ts b/src/machines/authMachine.ts index 5a7b4a0e9..3ea7a055f 100644 --- a/src/machines/authMachine.ts +++ b/src/machines/authMachine.ts @@ -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', + }) +} diff --git a/src/machines/commandBarMachine.ts b/src/machines/commandBarMachine.ts index 9ed5da137..6ecf29e7c 100644 --- a/src/machines/commandBarMachine.ts +++ b/src/machines/commandBarMachine.ts @@ -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) => diff --git a/src/machines/machineConstants.ts b/src/machines/machineConstants.ts new file mode 100644 index 000000000..a1a18ce4f --- /dev/null +++ b/src/machines/machineConstants.ts @@ -0,0 +1,3 @@ +export const ACTOR_IDS = { + AUTH: 'auth', +} diff --git a/src/routes/Onboarding/UserMenu.tsx b/src/routes/Onboarding/UserMenu.tsx index daec412bb..3487f3051 100644 --- a/src/routes/Onboarding/UserMenu.tsx +++ b/src/routes/Onboarding/UserMenu.tsx @@ -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' diff --git a/src/routes/SignIn.tsx b/src/routes/SignIn.tsx index bbfe23731..f78064c90 100644 --- a/src/routes/SignIn.tsx +++ b/src/routes/SignIn.tsx @@ -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 (