) {
- const { children, onClick } = props
+ const { children, disabled, onClick } = props
return (
{children}
@@ -66,11 +67,12 @@ function UserMenu() {
>
{organization != null ? (
<>
- {' '}
Signed in as {organization.name}
- Your profile
+
+ Your profile
+
{canChangePassword && (
{
diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/localBackend.ts b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/localBackend.ts
index d8947d16a9d..6f8235f208b 100644
--- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/localBackend.ts
+++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/localBackend.ts
@@ -102,11 +102,10 @@ export class LocalBackend implements Partial {
if (LocalBackend.currentlyOpeningProjectId === projectId) {
LocalBackend.currentlyOpeningProjectId = null
}
- await this.projectManager.closeProject({ projectId })
- if (projectId === LocalBackend.currentlyOpeningProjectId) {
- LocalBackend.currentlyOpeningProjectId = null
+ if (LocalBackend.currentlyOpenProject?.id === projectId) {
LocalBackend.currentlyOpenProject = null
}
+ await this.projectManager.closeProject({ projectId })
}
/** Close the project identified by the given project ID.
@@ -180,7 +179,9 @@ export class LocalBackend implements Partial {
projectId,
missingComponentAction: projectManager.MissingComponentAction.install,
})
- LocalBackend.currentlyOpenProject = { id: projectId, project }
+ if (LocalBackend.currentlyOpeningProjectId === projectId) {
+ LocalBackend.currentlyOpenProject = { id: projectId, project }
+ }
}
/** Change the name of a project.
@@ -232,9 +233,9 @@ export class LocalBackend implements Partial {
if (LocalBackend.currentlyOpeningProjectId === projectId) {
LocalBackend.currentlyOpeningProjectId = null
}
- await this.projectManager.deleteProject({ projectId })
if (LocalBackend.currentlyOpenProject?.id === projectId) {
LocalBackend.currentlyOpenProject = null
}
+ await this.projectManager.deleteProject({ projectId })
}
}
diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/validation.ts b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/validation.ts
index 70892601076..972154528c6 100644
--- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/validation.ts
+++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/validation.ts
@@ -8,3 +8,11 @@ export const PASSWORD_PATTERN =
export const PASSWORD_TITLE =
'Your password must include numbers, letters (both lowercase and uppercase) and symbols, ' +
'and must be between 6 and 256 characters long.'
+
+/** Regex pattern used by the backend for validating the previous password,
+ * when changing password. */
+export const PREVIOUS_PASSWORD_PATTERN = '^[\\S]+.*[\\S]+$'
+/** Human readable explanation of password requirements. */
+export const PREVIOUS_PASSWORD_TITLE =
+ 'Your password must neither start nor end with whitespace, and must contain ' +
+ 'at least two characters.'
diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/hooks.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/hooks.tsx
index 1400b684d4f..d7b41e94d9f 100644
--- a/app/ide-desktop/lib/dashboard/src/authentication/src/hooks.tsx
+++ b/app/ide-desktop/lib/dashboard/src/authentication/src/hooks.tsx
@@ -99,3 +99,52 @@ export function useNavigate() {
return navigate
}
+
+// =====================
+// === useDebugState ===
+// =====================
+
+/** A modified `useState` that logs the old and new values when `setState` is called. */
+export function useDebugState(
+ initialState: T | (() => T),
+ name?: string
+): [state: T, setState: (valueOrUpdater: react.SetStateAction, source?: string) => void] {
+ const [state, rawSetState] = react.useState(initialState)
+
+ const description = name != null ? `state for '${name}'` : 'state'
+
+ const setState = react.useCallback(
+ (valueOrUpdater: react.SetStateAction, source?: string) => {
+ const fullDescription = `${description}${source != null ? ` from '${source}'` : ''}`
+ if (typeof valueOrUpdater === 'function') {
+ // This is UNSAFE, however React makes the same assumption.
+ // eslint-disable-next-line no-restricted-syntax
+ const updater = valueOrUpdater as (prevState: T) => T
+ // `console.*` is allowed because this is for debugging purposes only.
+ /* eslint-disable no-restricted-properties */
+ rawSetState(oldState => {
+ console.group(description)
+ console.log(`Old ${fullDescription}:`, oldState)
+ const newState = updater(oldState)
+ console.log(`New ${fullDescription}:`, newState)
+ console.groupEnd()
+ return newState
+ })
+ } else {
+ rawSetState(oldState => {
+ if (!Object.is(oldState, valueOrUpdater)) {
+ console.group(description)
+ console.log(`Old ${fullDescription}:`, oldState)
+ console.log(`New ${fullDescription}:`, valueOrUpdater)
+ console.groupEnd()
+ }
+ return valueOrUpdater
+ })
+ /* eslint-enable no-restricted-properties */
+ }
+ },
+ []
+ )
+
+ return [state, setState]
+}
diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/providers/backend.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/providers/backend.tsx
index 921cee7b55d..a2ac1de22ca 100644
--- a/app/ide-desktop/lib/dashboard/src/authentication/src/providers/backend.tsx
+++ b/app/ide-desktop/lib/dashboard/src/authentication/src/providers/backend.tsx
@@ -2,9 +2,18 @@
* provider via the shared React context. */
import * as react from 'react'
+import * as common from 'enso-common'
+
import * as localBackend from '../dashboard/localBackend'
import * as remoteBackend from '../dashboard/remoteBackend'
+// =================
+// === Constants ===
+// =================
+
+/** The `localStorage` key under which the type of the current backend is stored. */
+export const BACKEND_TYPE_KEY = `${common.PRODUCT_NAME.toLowerCase()}-dashboard-backend-type`
+
// =============
// === Types ===
// =============
@@ -20,6 +29,7 @@ export type AnyBackendAPI = localBackend.LocalBackend | remoteBackend.RemoteBack
export interface BackendContextType {
backend: AnyBackendAPI
setBackend: (backend: AnyBackendAPI) => void
+ setBackendWithoutSavingType: (backend: AnyBackendAPI) => void
}
// @ts-expect-error The default value will never be exposed
@@ -37,12 +47,17 @@ export interface BackendProviderProps extends React.PropsWithChildren