diff --git a/package.json b/package.json
index fbedb7fe2..1f7aee7d3 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"@types/react-dom": "^18.0.0",
"@uiw/codemirror-extensions-langs": "^4.21.9",
"@uiw/react-codemirror": "^4.15.1",
+ "@xstate/react": "^3.2.2",
"crypto-js": "^4.1.1",
"formik": "^2.4.3",
"http-server": "^14.1.1",
@@ -42,6 +43,7 @@
"wasm-pack": "^0.12.1",
"web-vitals": "^2.1.0",
"ws": "^8.13.0",
+ "xstate": "^4.38.2",
"zustand": "^4.1.4"
},
"scripts": {
diff --git a/src/App.test.tsx b/src/App.test.tsx
index 9548e99b7..a943f1423 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react'
import { App } from './App'
import { describe, test, vi } from 'vitest'
import { BrowserRouter } from 'react-router-dom'
+import { GlobalStateProvider } from './hooks/useAuthMachine'
let listener: ((rect: any) => void) | undefined = undefined
;(global as any).ResizeObserver = class ResizeObserver {
@@ -27,9 +28,9 @@ describe('App tests', () => {
}
})
render(
-
+
-
+
)
const linkElement = screen.getByText(/Variables/i)
expect(linkElement).toBeInTheDocument()
@@ -37,3 +38,12 @@ describe('App tests', () => {
vi.restoreAllMocks()
})
})
+
+function TestWrap({ children }: { children: React.ReactNode }) {
+ // wrap in router and xState context
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/App.tsx b/src/App.tsx
index 7ad832516..cd3326d4e 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -48,6 +48,7 @@ import { writeTextFile } from '@tauri-apps/api/fs'
import { PROJECT_ENTRYPOINT } from './lib/tauriFS'
import { IndexLoaderData } from './Router'
import { toast } from 'react-hot-toast'
+import { useAuthMachine } from './hooks/useAuthMachine'
export function App() {
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
@@ -81,7 +82,6 @@ export function App() {
isMouseDownInStream,
cmdId,
setCmdId,
- token,
formatCode,
debugPanel,
theme,
@@ -121,7 +121,6 @@ export function App() {
isMouseDownInStream: s.isMouseDownInStream,
cmdId: s.cmdId,
setCmdId: s.setCmdId,
- token: s.token,
formatCode: s.formatCode,
debugPanel: s.debugPanel,
addKCLError: s.addKCLError,
@@ -134,6 +133,7 @@ export function App() {
setStreamDimensions: s.setStreamDimensions,
streamDimensions: s.streamDimensions,
}))
+ const [token] = useAuthMachine((s) => s?.context?.token)
const editorTheme = theme === Themes.System ? getSystemTheme() : theme
diff --git a/src/Auth.tsx b/src/Auth.tsx
index 8e672c1f9..b179ce4db 100644
--- a/src/Auth.tsx
+++ b/src/Auth.tsx
@@ -1,38 +1,11 @@
-import useSWR from 'swr'
-import fetcher from './lib/fetcher'
-import withBaseUrl from './lib/withBaseURL'
-import { User, useStore } from './useStore'
-import { useNavigate } from 'react-router-dom'
-import { useEffect } from 'react'
-import { isTauri } from './lib/isTauri'
import Loading from './components/Loading'
-import { paths } from './Router'
+import { useAuthMachine } from './hooks/useAuthMachine'
// Wrapper around protected routes, used in src/Router.tsx
export const Auth = ({ children }: React.PropsWithChildren) => {
- const { data: user, isLoading } = useSWR<
- User | Partial<{ error_code: string }>
- >(withBaseUrl('/user'), fetcher)
- const { token, setUser } = useStore((s) => ({
- token: s.token,
- setUser: s.setUser,
- }))
- const navigate = useNavigate()
+ const [isLoggedIn] = useAuthMachine((s) => s.matches('checkIfLoggedIn'))
- useEffect(() => {
- if (user && 'id' in user) setUser(user)
- }, [user, setUser])
-
- useEffect(() => {
- if (
- (isTauri() && !token) ||
- (!isTauri() && !isLoading && !(user && 'id' in user))
- ) {
- navigate(paths.SIGN_IN)
- }
- }, [user, token, navigate, isLoading])
-
- return isLoading ? (
+ return isLoggedIn ? (
Loading KittyCAD Modeling App...
) : (
<>{children}>
diff --git a/src/Router.tsx b/src/Router.tsx
index efdd43135..7cfd8062f 100644
--- a/src/Router.tsx
+++ b/src/Router.tsx
@@ -24,6 +24,7 @@ import {
} from './lib/tauriFS'
import { metadata, type Metadata } from 'tauri-plugin-fs-extra-api'
import DownloadAppBanner from './components/DownloadAppBanner'
+import { GlobalStateProvider } from './hooks/useAuthMachine'
const prependRoutes =
(routesObject: Record) => (prepend: string) => {
@@ -58,124 +59,142 @@ export type HomeLoaderData = {
projects: ProjectWithEntryPointMetadata[]
}
-const router = createBrowserRouter([
- {
- path: paths.INDEX,
- loader: () =>
- isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'),
- },
- {
- path: paths.FILE + '/:id',
- element: (
-
-
-
- {!isTauri() && import.meta.env.PROD && }
-
- ),
- errorElement: ,
- id: paths.FILE,
- loader: async ({
- request,
- params,
- }): Promise => {
- const store = localStorage.getItem('store')
- if (store === null) {
- return redirect(paths.ONBOARDING.INDEX)
- } else {
- const status = JSON.parse(store).state.onboardingStatus || ''
- const notEnRouteToOnboarding =
- !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
+type CreateBrowserRouterArg = Parameters[0]
- if (shouldRedirectToOnboarding) {
- return redirect(makeUrlPathRelative(paths.ONBOARDING.INDEX) + status)
+const addGlobalContextToElements = (
+ routes: CreateBrowserRouterArg
+): CreateBrowserRouterArg =>
+ routes.map((route) =>
+ 'element' in route
+ ? {
+ ...route,
+ element: {route.element},
}
- }
+ : route
+ )
- if (params.id && params.id !== 'new') {
- // Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
- const code = await readTextFile(params.id + '/' + PROJECT_ENTRYPOINT)
- const entrypoint_metadata = await metadata(
- params.id + '/' + PROJECT_ENTRYPOINT
- )
- const children = await readDir(params.id)
+const router = createBrowserRouter(
+ addGlobalContextToElements([
+ {
+ path: paths.INDEX,
+ loader: () =>
+ isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'),
+ },
+ {
+ path: paths.FILE + '/:id',
+ element: (
+
+
+
+ {!isTauri() && import.meta.env.PROD && }
+
+ ),
+ errorElement: ,
+ id: paths.FILE,
+ loader: async ({
+ request,
+ params,
+ }): Promise => {
+ const store = localStorage.getItem('store')
+ if (store === null) {
+ return redirect(paths.ONBOARDING.INDEX)
+ } else {
+ const status = JSON.parse(store).state.onboardingStatus || ''
+ const notEnRouteToOnboarding =
+ !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(
+ makeUrlPathRelative(paths.ONBOARDING.INDEX) + status
+ )
+ }
+ }
+
+ if (params.id && params.id !== 'new') {
+ // Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
+ const code = await readTextFile(params.id + '/' + PROJECT_ENTRYPOINT)
+ const entrypoint_metadata = await metadata(
+ params.id + '/' + PROJECT_ENTRYPOINT
+ )
+ const children = await readDir(params.id)
+
+ return {
+ code,
+ project: {
+ name: params.id.slice(params.id.lastIndexOf('/') + 1),
+ path: params.id,
+ children,
+ entrypoint_metadata,
+ },
+ }
+ }
return {
- code,
- project: {
- name: params.id.slice(params.id.lastIndexOf('/') + 1),
- path: params.id,
- children,
- entrypoint_metadata,
- },
+ code: '',
}
- }
-
- return {
- code: '',
- }
+ },
+ children: [
+ {
+ path: makeUrlPathRelative(paths.SETTINGS),
+ element: ,
+ },
+ {
+ path: makeUrlPathRelative(paths.ONBOARDING.INDEX),
+ element: ,
+ children: onboardingRoutes,
+ },
+ ],
},
- children: [
- {
- path: makeUrlPathRelative(paths.SETTINGS),
- element: ,
- },
- {
- path: makeUrlPathRelative(paths.ONBOARDING.INDEX),
- element: ,
- children: onboardingRoutes,
- },
- ],
- },
- {
- path: paths.HOME,
- element: (
-
-
-
-
- ),
- loader: async () => {
- if (!isTauri()) {
- return redirect(paths.FILE + '/new')
- }
+ {
+ path: paths.HOME,
+ element: (
+
+
+
+
+ ),
+ loader: async () => {
+ if (!isTauri()) {
+ return redirect(paths.FILE + '/new')
+ }
- const projectDir = await initializeProjectDirectory()
- const projectsNoMeta = (await readDir(projectDir.dir)).filter(
- isProjectDirectory
- )
- const projects = await Promise.all(
- projectsNoMeta.map(async (p) => ({
- entrypoint_metadata: await metadata(
- p.path + '/' + PROJECT_ENTRYPOINT
- ),
- ...p,
- }))
- )
+ const projectDir = await initializeProjectDirectory()
+ const projectsNoMeta = (await readDir(projectDir.dir)).filter(
+ isProjectDirectory
+ )
+ const projects = await Promise.all(
+ projectsNoMeta.map(async (p) => ({
+ entrypoint_metadata: await metadata(
+ p.path + '/' + PROJECT_ENTRYPOINT
+ ),
+ ...p,
+ }))
+ )
- return {
- projects,
- }
+ return {
+ projects,
+ }
+ },
+ children: [
+ {
+ path: makeUrlPathRelative(paths.SETTINGS),
+ element: ,
+ },
+ ],
},
- children: [
- {
- path: makeUrlPathRelative(paths.SETTINGS),
- element: ,
- },
- ],
- },
- {
- path: paths.SIGN_IN,
- element: ,
- },
-])
+ {
+ path: paths.SIGN_IN,
+ element: ,
+ },
+ ])
+)
/**
* All routes in the app, used in src/index.tsx
diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx
index 056911ea6..d80a92b2a 100644
--- a/src/components/AppHeader.tsx
+++ b/src/components/AppHeader.tsx
@@ -1,8 +1,8 @@
import { Toolbar } from '../Toolbar'
-import { useStore } from '../useStore'
import UserSidebarMenu from './UserSidebarMenu'
import { ProjectWithEntryPointMetadata } from '../Router'
import ProjectSidebarMenu from './ProjectSidebarMenu'
+import { useAuthMachine } from '../hooks/useAuthMachine'
interface AppHeaderProps extends React.PropsWithChildren {
showToolbar?: boolean
@@ -18,9 +18,7 @@ export const AppHeader = ({
className = '',
enableMenu = false,
}: AppHeaderProps) => {
- const { user } = useStore((s) => ({
- user: s.user,
- }))
+ const [user] = useAuthMachine((s) => s?.context?.user)
return (
{
test("Renders user's name and email if available", () => {
@@ -12,12 +15,18 @@ describe('UserSidebarMenu tests', () => {
image: 'https://placekitten.com/200/200',
created_at: 'yesteryear',
updated_at: 'today',
+ company: 'Test Company',
+ discord: 'Test User#1234',
+ github: 'testuser',
+ phone: '555-555-5555',
+ first_name: 'Test',
+ last_name: 'User',
}
render(
-
+
-
+
)
fireEvent.click(screen.getByTestId('user-sidebar-toggle'))
@@ -35,12 +44,19 @@ describe('UserSidebarMenu tests', () => {
image: 'https://placekitten.com/200/200',
created_at: 'yesteryear',
updated_at: 'today',
+ company: 'Test Company',
+ discord: 'Test User#1234',
+ github: 'testuser',
+ phone: '555-555-5555',
+ first_name: '',
+ last_name: '',
+ name: '',
}
render(
-
+
-
+
)
fireEvent.click(screen.getByTestId('user-sidebar-toggle'))
@@ -55,14 +71,30 @@ describe('UserSidebarMenu tests', () => {
email: 'kittycad.sidebar.test@example.com',
created_at: 'yesteryear',
updated_at: 'today',
+ company: 'Test Company',
+ discord: 'Test User#1234',
+ github: 'testuser',
+ phone: '555-555-5555',
+ first_name: 'Test',
+ last_name: 'User',
+ image: '',
}
render(
-
+
-
+
)
expect(screen.getByTestId('user-sidebar-toggle')).toHaveTextContent('Menu')
})
})
+
+function TestWrap({ children }: { children: React.ReactNode }) {
+ // wrap in router and xState context
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/components/UserSidebarMenu.tsx b/src/components/UserSidebarMenu.tsx
index 75d46fff7..9134c949c 100644
--- a/src/components/UserSidebarMenu.tsx
+++ b/src/components/UserSidebarMenu.tsx
@@ -1,5 +1,4 @@
import { Popover } from '@headlessui/react'
-import { User, useStore } from '../useStore'
import { ActionButton } from './ActionButton'
import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
import { faGithub } from '@fortawesome/free-brands-svg-icons'
@@ -7,14 +6,16 @@ import { useNavigate } from 'react-router-dom'
import { useState } from 'react'
import { paths } from '../Router'
import makeUrlPathRelative from '../lib/makeUrlPathRelative'
+import { useAuthMachine } from '../hooks/useAuthMachine'
+import { Models } from '@kittycad/lib'
+
+type User = Models['User_type']
const UserSidebarMenu = ({ user }: { user?: User }) => {
const displayedName = getDisplayName(user)
const [imageLoadFailed, setImageLoadFailed] = useState(false)
const navigate = useNavigate()
- const { setToken } = useStore((s) => ({
- setToken: s.setToken,
- }))
+ const [_, send] = useAuthMachine()
// Fallback logic for displaying user's "name":
// 1. user.name
@@ -119,10 +120,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
{
- setToken('')
- navigate(paths.SIGN_IN)
- }}
+ onClick={() => send('logout')}
icon={{
icon: faSignOutAlt,
bgClassName: 'bg-destroy-80',
diff --git a/src/hooks/useAuthMachine.tsx b/src/hooks/useAuthMachine.tsx
new file mode 100644
index 000000000..11c07c458
--- /dev/null
+++ b/src/hooks/useAuthMachine.tsx
@@ -0,0 +1,54 @@
+import { createActorContext } from '@xstate/react'
+import { useNavigate } from 'react-router-dom'
+import { paths } from '../Router'
+import { authMachine, TOKEN_PERSIST_KEY } from '../lib/authMachine'
+import withBaseUrl from '../lib/withBaseURL'
+
+export const AuthMachineContext = createActorContext(authMachine)
+
+export const GlobalStateProvider = ({
+ children,
+}: {
+ children: React.ReactNode
+}) => {
+ const navigate = useNavigate()
+ return (
+
+ authMachine.withConfig({
+ actions: {
+ goToSignInPage: () => {
+ navigate(paths.SIGN_IN)
+ logout()
+ },
+ goToIndexPage: () => navigate(paths.INDEX),
+ },
+ })
+ }
+ >
+ {children}
+
+ )
+}
+
+export function useAuthMachine(
+ selector: (
+ state: Parameters[0]>[0]
+ ) => T = () => null as T
+): [T, ReturnType[1]] {
+ // useActor api normally `[state, send] = useActor`
+ // we're only interested in send because of the selector
+ const send = AuthMachineContext.useActor()[1]
+
+ const selection = AuthMachineContext.useSelector(selector)
+ return [selection, send]
+}
+
+export function logout() {
+ const url = withBaseUrl('/logout')
+ localStorage.removeItem(TOKEN_PERSIST_KEY)
+ return fetch(url, {
+ method: 'POST',
+ credentials: 'include',
+ })
+}
diff --git a/src/lang/std/engineConnection.ts b/src/lang/std/engineConnection.ts
index c7795ba3d..bf98fafcf 100644
--- a/src/lang/std/engineConnection.ts
+++ b/src/lang/std/engineConnection.ts
@@ -363,7 +363,6 @@ export class EngineCommandManager {
this.onHoverCallback = callback
}
onClick(callback: (selection?: SelectionsArgs) => void) {
- // TODO talk to the gang about this
// It's when the user clicks on a part in the 3d scene, and so the engine should tell the
// frontend about that (with it's id) so that the FE can put the user's cursor on the right
// line of code
diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts
index 55a987cff..61de8820b 100644
--- a/src/lang/std/sketch.ts
+++ b/src/lang/std/sketch.ts
@@ -136,11 +136,6 @@ export const lineTo: SketchLineHelper = {
sourceRange,
data,
})
- // engineCommandManager.sendModellingCommand({
- // id,
- // params: [lineData, previousSketch],
- // range: sourceRange,
- // })
const currentPath: Path = {
type: 'toPoint',
to,
@@ -673,11 +668,6 @@ export const angledLine: SketchLineHelper = {
sourceRange,
data,
})
- // engineCommandManager.sendModellingCommand({
- // id,
- // params: [lineData, previousSketch],
- // range: sourceRange,
- // })
const currentPath: Path = {
type: 'toPoint',
to,
diff --git a/src/lib/authMachine.ts b/src/lib/authMachine.ts
new file mode 100644
index 000000000..fc37fa22b
--- /dev/null
+++ b/src/lib/authMachine.ts
@@ -0,0 +1,102 @@
+import { createMachine, assign } from 'xstate'
+import { Models } from '@kittycad/lib'
+import withBaseURL from '../lib/withBaseURL'
+
+export interface UserContext {
+ user?: Models['User_type']
+ token?: string
+}
+
+export type Events =
+ | {
+ type: 'logout'
+ }
+ | {
+ type: 'tryLogin'
+ token?: string
+ }
+
+export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY'
+const persistedToken = localStorage?.getItem(TOKEN_PERSIST_KEY) || ''
+
+export const authMachine = createMachine(
+ {
+ id: 'Auth',
+ initial: 'checkIfLoggedIn',
+ states: {
+ checkIfLoggedIn: {
+ id: 'check-if-logged-in',
+ invoke: {
+ src: 'getUser',
+ id: 'check-logged-in',
+ onDone: [
+ {
+ target: 'loggedIn',
+ actions: assign({
+ user: (context, event) => event.data,
+ }),
+ },
+ ],
+ onError: [
+ {
+ target: 'loggedOut',
+ actions: assign({
+ user: () => undefined,
+ }),
+ },
+ ],
+ },
+ },
+ loggedIn: {
+ entry: ['goToIndexPage'],
+ on: {
+ logout: {
+ target: 'loggedOut',
+ },
+ },
+ },
+ loggedOut: {
+ entry: ['goToSignInPage'],
+ on: {
+ tryLogin: {
+ target: 'checkIfLoggedIn',
+ actions: assign({
+ token: (context, event) => {
+ const token = event.token || ''
+ localStorage.setItem(TOKEN_PERSIST_KEY, token)
+ return token
+ },
+ }),
+ },
+ },
+ },
+ },
+ schema: { events: {} as { type: 'logout' } | { type: 'tryLogin' } },
+ predictableActionArguments: true,
+ preserveActionOrder: true,
+ context: { token: persistedToken },
+ },
+ {
+ actions: {},
+ services: { getUser },
+ guards: {},
+ delays: {},
+ }
+)
+
+async function getUser(context: UserContext) {
+ const url = withBaseURL('/user')
+ const headers: { [key: string]: string } = {
+ 'Content-Type': 'application/json',
+ }
+ if (!context.token && '__TAURI__' in window) throw 'not log in'
+ if (context.token) headers['Authorization'] = `Bearer ${context.token}`
+ const response = await fetch(url, {
+ method: 'GET',
+ credentials: 'include',
+ headers,
+ })
+ const user = await response.json()
+ if ('error_code' in user) throw new Error(user.message)
+ return user
+}
diff --git a/src/lib/fetcher.ts b/src/lib/fetcher.ts
index dd9c6b34c..4bedb6928 100644
--- a/src/lib/fetcher.ts
+++ b/src/lib/fetcher.ts
@@ -1,10 +1,10 @@
-import { useStore } from '../useStore'
+import { useAuthMachine } from '../hooks/useAuthMachine'
export default async function fetcher(
input: RequestInfo,
init: RequestInit = {}
): Promise {
- const { token } = useStore.getState()
+ const [token] = useAuthMachine((s) => s?.context?.token)
const headers = { ...init.headers } as Record
if (token) {
headers.Authorization = `Bearer ${token}`
diff --git a/src/routes/SignIn.tsx b/src/routes/SignIn.tsx
index de78b346f..843f247a5 100644
--- a/src/routes/SignIn.tsx
+++ b/src/routes/SignIn.tsx
@@ -7,13 +7,14 @@ import { useNavigate } from 'react-router-dom'
import { VITE_KC_SITE_BASE_URL, VITE_KC_API_BASE_URL } from '../env'
import { getSystemTheme } from '../lib/getSystemTheme'
import { paths } from '../Router'
+import { useAuthMachine } from '../hooks/useAuthMachine'
const SignIn = () => {
const navigate = useNavigate()
- const { setToken, theme } = useStore((s) => ({
- setToken: s.setToken,
+ const { theme } = useStore((s) => ({
theme: s.theme,
}))
+ const [_, send] = useAuthMachine()
const appliedTheme = theme === Themes.System ? getSystemTheme() : theme
const signInTauri = async () => {
// We want to invoke our command to login via device auth.
@@ -21,8 +22,7 @@ const SignIn = () => {
const token: string = await invoke('login', {
host: VITE_KC_API_BASE_URL,
})
- setToken(token)
- navigate(paths.INDEX)
+ send({ type: 'tryLogin', token })
} catch (error) {
console.error('login button', error)
}
diff --git a/src/useStore.ts b/src/useStore.ts
index abf49fe6b..a8ecb1d52 100644
--- a/src/useStore.ts
+++ b/src/useStore.ts
@@ -114,20 +114,6 @@ interface DefaultDir {
export type PaneType = 'code' | 'variables' | 'debug' | 'kclErrors' | 'logs'
-// TODO: import real OpenAPI User type from schema
-export interface User {
- company?: string
- created_at: string
- email: string
- first_name?: string
- id: string
- image?: string
- last_name?: string
- name?: string
- phone?: string
- updated_at: string
-}
-
export interface StoreState {
editorView: EditorView | null
setEditorView: (editorView: EditorView) => void
@@ -219,10 +205,6 @@ export interface StoreState {
path: string
}[]
setHomeMenuItems: (items: { name: string; path: string }[]) => void
- token: string
- setToken: (token: string) => void
- user?: User
- setUser: (user: User | undefined) => void
debugPanel: boolean
setDebugPanel: (debugPanel: boolean) => void
}
@@ -423,10 +405,6 @@ export const useStore = create()(
setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }),
homeMenuItems: [],
setHomeMenuItems: (homeMenuItems) => set({ homeMenuItems }),
- token: '',
- setToken: (token) => set({ token }),
- user: undefined,
- setUser: (user) => set({ user }),
debugPanel: false,
setDebugPanel: (debugPanel) => set({ debugPanel }),
}),
@@ -441,7 +419,6 @@ export const useStore = create()(
'defaultProjectName',
'defaultUnitSystem',
'defaultBaseUnit',
- 'token',
'debugPanel',
'onboardingStatus',
'theme',
diff --git a/yarn.lock b/yarn.lock
index c5ebd3533..ff074d0bc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2506,6 +2506,14 @@
loupe "^2.3.6"
pretty-format "^29.5.0"
+"@xstate/react@^3.2.2":
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/@xstate/react/-/react-3.2.2.tgz#ddf0f9d75e2c19375b1e1b7335e72cb99762aed8"
+ integrity sha512-feghXWLedyq8JeL13yda3XnHPZKwYDN5HPBLykpLeuNpr9178tQd2/3d0NrH6gSd0sG5mLuLeuD+ck830fgzLQ==
+ dependencies:
+ use-isomorphic-layout-effect "^1.1.2"
+ use-sync-external-store "^1.0.0"
+
acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
@@ -5893,7 +5901,7 @@ use-composed-ref@^1.3.0:
resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda"
integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==
-use-isomorphic-layout-effect@^1.1.1:
+use-isomorphic-layout-effect@^1.1.1, use-isomorphic-layout-effect@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb"
integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==
@@ -5905,7 +5913,7 @@ use-latest@^1.2.1:
dependencies:
use-isomorphic-layout-effect "^1.1.1"
-use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0:
+use-sync-external-store@1.2.0, use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
@@ -6112,6 +6120,11 @@ ws@^8.13.0:
resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
+xstate@^4.38.2:
+ version "4.38.2"
+ resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.38.2.tgz#1b74544fc9c8c6c713ba77f81c6017e65aa89804"
+ integrity sha512-Fba/DwEPDLneHT3tbJ9F3zafbQXszOlyCJyQqqdzmtlY/cwE2th462KK48yaANf98jHlP6lJvxfNtN0LFKXPQg==
+
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"