From 4341f0161abfafa288840bf1b4426df271e5f924 Mon Sep 17 00:00:00 2001
From: Denis Bykhov <bykhov.denis@gmail.com>
Date: Sun, 18 Feb 2024 21:23:04 +0600
Subject: [PATCH] ONB-23 (#4690)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
---
 plugins/login-resources/src/actions.ts        | 30 +++++++++
 .../src/components/Confirmation.svelte        | 32 +++------
 .../src/components/ConfirmationSend.svelte    | 17 ++---
 .../src/components/CreateWorkspaceForm.svelte | 13 ++--
 .../src/components/Form.svelte                | 30 ++++-----
 .../src/components/InviteLink.svelte          |  2 +-
 .../src/components/Join.svelte                | 38 +++--------
 .../src/components/LoginApp.svelte            | 35 +++-------
 .../src/components/LoginForm.svelte           | 33 +++-------
 .../src/components/PasswordRequest.svelte     | 29 +++-----
 .../src/components/PasswordRestore.svelte     | 13 ++--
 .../src/components/SelectWorkspace.svelte     | 66 +++++++------------
 .../src/components/SignupForm.svelte          |  9 +--
 plugins/login-resources/src/index.ts          | 22 +++++++
 plugins/login-resources/src/utils.ts          | 54 +++++++++++++--
 .../src/components/SelectWorkspaceMenu.svelte |  1 +
 server/account/src/index.ts                   |  2 +-
 .../tests/model/select-workspace-page.ts      |  1 +
 tests/sanity/tests/workspace/create.spec.ts   |  5 --
 19 files changed, 207 insertions(+), 225 deletions(-)
 create mode 100644 plugins/login-resources/src/actions.ts

diff --git a/plugins/login-resources/src/actions.ts b/plugins/login-resources/src/actions.ts
new file mode 100644
index 0000000000..4a0bab82bc
--- /dev/null
+++ b/plugins/login-resources/src/actions.ts
@@ -0,0 +1,30 @@
+import { goTo } from './utils'
+import login from './plugin'
+import { type BottomAction } from '.'
+
+export const signUpAction: BottomAction = {
+  caption: login.string.DoNotHaveAnAccount,
+  i18n: login.string.SignUp,
+  page: 'signup',
+  func: () => {
+    goTo('signup')
+  }
+}
+
+export const loginAction: BottomAction = {
+  caption: login.string.AlreadyJoined,
+  i18n: login.string.LogIn,
+  page: 'login',
+  func: () => {
+    goTo('login', true)
+  }
+}
+
+export const recoveryAction: BottomAction = {
+  caption: login.string.ForgotPassword,
+  i18n: login.string.Recover,
+  page: 'password',
+  func: () => {
+    goTo('password', true)
+  }
+}
diff --git a/plugins/login-resources/src/components/Confirmation.svelte b/plugins/login-resources/src/components/Confirmation.svelte
index 43a250ed94..4f2d9366c7 100644
--- a/plugins/login-resources/src/components/Confirmation.svelte
+++ b/plugins/login-resources/src/components/Confirmation.svelte
@@ -13,32 +13,16 @@
 // limitations under the License.
 -->
 <script lang="ts">
-  import { OK, setMetadata, Severity, Status } from '@hcengineering/platform'
+  import { OK, Severity, Status, setMetadata } from '@hcengineering/platform'
 
-  import { getCurrentLocation, navigate, setMetadataLocalStorage } from '@hcengineering/ui'
-  import login from '../plugin'
-  import { confirm } from '../utils'
-  import presentation from '@hcengineering/presentation'
+  import { getCurrentLocation, setMetadataLocalStorage } from '@hcengineering/ui'
   import { onMount } from 'svelte'
+  import login from '../plugin'
+  import { afterConfirm, confirm, goTo } from '../utils'
+  import presentation from '@hcengineering/presentation'
 
   export let status: Status<any> = OK
 
-  function goToWorkspaces () {
-    const loc = getCurrentLocation()
-    loc.query = undefined
-    loc.path[1] = 'selectWorkspace'
-    loc.path.length = 2
-    navigate(loc)
-  }
-
-  function goToLogin (): void {
-    const loc = getCurrentLocation()
-    loc.query = undefined
-    loc.path[1] = 'login'
-    loc.path.length = 2
-    navigate(loc)
-  }
-
   async function check (): Promise<void> {
     const location = getCurrentLocation()
     if (location.query?.id === undefined || location.query?.id === null) return
@@ -53,13 +37,13 @@
       setMetadataLocalStorage(login.metadata.LastToken, result.token)
       setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
       setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
-      goToWorkspaces()
+      await afterConfirm()
     } else {
-      goToLogin()
+      goTo('login')
     }
   }
 
   onMount(() => {
-    check()
+    void check()
   })
 </script>
diff --git a/plugins/login-resources/src/components/ConfirmationSend.svelte b/plugins/login-resources/src/components/ConfirmationSend.svelte
index c793e52036..90800e3023 100644
--- a/plugins/login-resources/src/components/ConfirmationSend.svelte
+++ b/plugins/login-resources/src/components/ConfirmationSend.svelte
@@ -13,26 +13,23 @@
 // limitations under the License.
 -->
 <script lang="ts">
-  import { Label, getCurrentLocation, navigate } from '@hcengineering/ui'
-  import login from '../plugin'
-  import { getAccount } from '../utils'
+  import { Label } from '@hcengineering/ui'
   import { onMount } from 'svelte'
+  import login from '../plugin'
+  import { afterConfirm, getAccount } from '../utils'
 
   const CHECK_INTERVAL = 1000
 
-  async function checkAccountStatus () {
+  async function checkAccountStatus (): Promise<void> {
     const account = await getAccount()
     if (account?.confirmed === true) {
-      const loc = getCurrentLocation()
-      loc.path[1] = 'selectWorkspace'
-      loc.path.length = 2
-      navigate(loc)
+      await afterConfirm()
     }
   }
 
   let weAreHere = false
 
-  async function check () {
+  async function check (): Promise<void> {
     try {
       await checkAccountStatus()
     } catch (e) {
@@ -45,7 +42,7 @@
 
   onMount(() => {
     weAreHere = true
-    check()
+    void check()
     return () => {
       weAreHere = false
     }
diff --git a/plugins/login-resources/src/components/CreateWorkspaceForm.svelte b/plugins/login-resources/src/components/CreateWorkspaceForm.svelte
index 11c384111d..60b7910a3a 100644
--- a/plugins/login-resources/src/components/CreateWorkspaceForm.svelte
+++ b/plugins/login-resources/src/components/CreateWorkspaceForm.svelte
@@ -17,7 +17,7 @@
   import { Status, Severity, OK, setMetadata } from '@hcengineering/platform'
 
   import Form from './Form.svelte'
-  import { createWorkspace, getAccount } from '../utils'
+  import { createWorkspace, getAccount, goTo } from '../utils'
   import { fetchMetadataLocalStorage, getCurrentLocation, navigate, setMetadataLocalStorage } from '@hcengineering/ui'
   import login from '../plugin'
   import { workbenchId } from '@hcengineering/workbench'
@@ -26,6 +26,7 @@
 
   const fields = [
     {
+      id: 'workspace',
       name: 'workspace',
       i18n: login.string.Workspace,
       rules: []
@@ -40,7 +41,7 @@
 
   onMount(async () => {
     const account = await getAccount()
-    if (account?.confirmed !== true) {
+    if (account?.confirmed === false) {
       const loc = getCurrentLocation()
       loc.path[1] = 'confirmationSend'
       loc.path.length = 2
@@ -60,7 +61,7 @@
         setMetadata(presentation.metadata.Token, result.token)
         setMetadataLocalStorage(login.metadata.LastToken, result.token)
         const tokens: Record<string, string> = fetchMetadataLocalStorage(login.metadata.LoginTokens) ?? {}
-        tokens[object.workspace] = result.token
+        tokens[result.workspace] = result.token
         setMetadataLocalStorage(login.metadata.LoginTokens, tokens)
         setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
         setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
@@ -80,11 +81,9 @@
     {
       caption: login.string.HaveWorkspace,
       i18n: login.string.SelectWorkspace,
+      page: 'selectWorkspace',
       func: () => {
-        const loc = getCurrentLocation()
-        loc.path[1] = 'selectWorkspace'
-        loc.path.length = 2
-        navigate(loc)
+        goTo('selectWorkspace')
       }
     }
   ]}
diff --git a/plugins/login-resources/src/components/Form.svelte b/plugins/login-resources/src/components/Form.svelte
index ca0fac03bb..d423f4e891 100644
--- a/plugins/login-resources/src/components/Form.svelte
+++ b/plugins/login-resources/src/components/Form.svelte
@@ -14,21 +14,23 @@
 // limitations under the License.
 -->
 <script lang="ts">
+  import type { IntlString } from '@hcengineering/platform'
+  import { OK, Severity, Status, translate } from '@hcengineering/platform'
   import {
+    Button,
+    Label,
+    StylishEdit,
+    deviceOptionsStore as deviceInfo,
     getCurrentLocation,
     navigate,
-    StylishEdit,
-    Label,
-    Button,
-    deviceOptionsStore as deviceInfo,
     themeStore
   } from '@hcengineering/ui'
   import StatusControl from './StatusControl.svelte'
-  import { OK, Status, Severity, translate } from '@hcengineering/platform'
-  import type { IntlString } from '@hcengineering/platform'
 
-  import login from '../plugin'
+  import { NavLink } from '@hcengineering/presentation'
   import { onMount } from 'svelte'
+  import { BottomAction, getHref } from '..'
+  import login from '../plugin'
 
   interface Field {
     id?: string
@@ -49,12 +51,6 @@
     func: () => Promise<void>
   }
 
-  interface BottomAction {
-    i18n: IntlString
-    func: () => void
-    caption: IntlString
-  }
-
   export let caption: IntlString
   export let status: Status
   export let fields: Field[]
@@ -107,7 +103,7 @@
 
   let inAction = false
 
-  function performAction (action: Action) {
+  function performAction (action: Action): void {
     for (const field of fields) {
       trim(field.name)
     }
@@ -223,7 +219,11 @@
       {#each bottomActions as action}
         <div>
           <span><Label label={action.caption} /></span>
-          <a href="." on:click|preventDefault={action.func}><Label label={action.i18n} /></a>
+          {#if action.page}
+            <NavLink href={getHref(action.page)}><Label label={action.i18n} /></NavLink>
+          {:else}
+            <a href="." on:click|preventDefault={action.func}><Label label={action.i18n} /></a>
+          {/if}
         </div>
       {/each}
     </div>
diff --git a/plugins/login-resources/src/components/InviteLink.svelte b/plugins/login-resources/src/components/InviteLink.svelte
index eb8ec77fbb..4a32140135 100644
--- a/plugins/login-resources/src/components/InviteLink.svelte
+++ b/plugins/login-resources/src/components/InviteLink.svelte
@@ -61,7 +61,7 @@
     }
   })
 
-  function setToDefault () {
+  function setToDefault (): void {
     expHours = defaultValues.expirationTime
     emailMask = defaultValues.emailMask
     limit = defaultValues.limit
diff --git a/plugins/login-resources/src/components/Join.svelte b/plugins/login-resources/src/components/Join.svelte
index 5a7213ecb8..617f8a89b8 100644
--- a/plugins/login-resources/src/components/Join.svelte
+++ b/plugins/login-resources/src/components/Join.svelte
@@ -23,6 +23,8 @@
   import presentation from '@hcengineering/presentation'
   import { workbenchId } from '@hcengineering/workbench'
   import { onMount } from 'svelte'
+  import { BottomAction } from '..'
+  import { loginAction, recoveryAction } from '../actions'
   import login from '../plugin'
 
   const location = getCurrentLocation()
@@ -88,46 +90,22 @@
     }
   }
 
-  $: bottom = page === 'login' ? [signUpAction] : [loginJoinAction]
-  $: secondaryButtonLabel = page === 'login' ? login.string.SignUp : undefined
-  $: secondaryButtonAction = () => {
-    page = 'signUp'
-  }
-
-  const signUpAction = {
+  const signUpAction: BottomAction = {
     caption: login.string.DoNotHaveAnAccount,
     i18n: login.string.SignUp,
     func: () => (page = 'signUp')
   }
 
-  const loginJoinAction = {
+  const loginJoinAction: BottomAction = {
     caption: login.string.HaveAccount,
     i18n: login.string.LogIn,
     func: () => (page = 'login')
   }
 
-  const loginAction = {
-    caption: login.string.AlreadyJoined,
-    i18n: login.string.LogIn,
-    func: () => {
-      const loc = getCurrentLocation()
-      loc.path[1] = 'login'
-      loc.query = undefined
-      loc.path.length = 2
-      navigate(loc)
-    }
-  }
-
-  const recoveryAction = {
-    caption: login.string.ForgotPassword,
-    i18n: login.string.Recover,
-    func: () => {
-      const loc = getCurrentLocation()
-      loc.path[1] = 'password'
-      loc.query = undefined
-      loc.path.length = 2
-      navigate(loc)
-    }
+  $: bottom = page === 'login' ? [signUpAction] : [loginJoinAction]
+  $: secondaryButtonLabel = page === 'login' ? login.string.SignUp : undefined
+  $: secondaryButtonAction = () => {
+    page = 'signUp'
   }
 
   onMount(() => {
diff --git a/plugins/login-resources/src/components/LoginApp.svelte b/plugins/login-resources/src/components/LoginApp.svelte
index afaeb9546a..0576872b3b 100644
--- a/plugins/login-resources/src/components/LoginApp.svelte
+++ b/plugins/login-resources/src/components/LoginApp.svelte
@@ -14,7 +14,7 @@
 // limitations under the License.
 -->
 <script lang="ts">
-  import { Popup, Scroller, deviceOptionsStore as deviceInfo, location, ticker, themeStore } from '@hcengineering/ui'
+  import { Popup, Scroller, deviceOptionsStore as deviceInfo, location, themeStore } from '@hcengineering/ui'
 
   import { getMetadata } from '@hcengineering/platform'
   import presentation from '@hcengineering/presentation'
@@ -37,40 +37,23 @@
   import loginBackAvif from '../../img/login_back.avif'
   import loginBack2xAvif from '../../img/login_back_2x.avif'
 
+  import { Pages, pages } from '..'
   import loginBackWebp from '../../img/login_back.webp'
   import loginBack2xWebp from '../../img/login_back_2x.webp'
 
-  export let page: string = 'login'
+  export let page: Pages = 'login'
 
   let navigateUrl: string | undefined
 
-  function getToken (timer: number): string | undefined {
-    return getMetadata(presentation.metadata.Token)
-  }
-  $: token = getToken($ticker)
-
-  const pages = [
-    'login',
-    'signup',
-    'createWorkspace',
-    'password',
-    'recovery',
-    'selectWorkspace',
-    'join',
-    'confirm',
-    'confirmationSend'
-  ]
   onDestroy(
     location.subscribe((loc) => {
-      void (async (loc) => {
-        token = getMetadata(presentation.metadata.Token)
-        page = loc.path[1] ?? (token ? 'selectWorkspace' : 'login')
-        if (!pages.includes(page)) {
-          page = 'login'
-        }
+      const token = getMetadata(presentation.metadata.Token)
+      page = (loc.path[1] as Pages) ?? (token != null ? 'selectWorkspace' : 'login')
+      if (!pages.includes(page)) {
+        page = 'login'
+      }
 
-        navigateUrl = loc.query?.navigateUrl ?? undefined
-      })(loc)
+      navigateUrl = loc.query?.navigateUrl ?? undefined
     })
   )
 </script>
diff --git a/plugins/login-resources/src/components/LoginForm.svelte b/plugins/login-resources/src/components/LoginForm.svelte
index 2c7aa383f0..5e347307e6 100644
--- a/plugins/login-resources/src/components/LoginForm.svelte
+++ b/plugins/login-resources/src/components/LoginForm.svelte
@@ -30,6 +30,7 @@
   import Form from './Form.svelte'
 
   import { LoginInfo } from '@hcengineering/login'
+  import { recoveryAction } from '../actions'
   import login from '../plugin'
 
   export let navigateUrl: string | undefined = undefined
@@ -51,15 +52,11 @@
 
   async function doLoginNavigate (
     result: LoginInfo | undefined,
-    updateStatus: (status: Status<any>) => void,
-    token?: string
+    updateStatus: (status: Status<any>) => void
   ): Promise<boolean> {
     if (result !== undefined) {
-      if (result.token != null && getMetadata(presentation.metadata.Token) === result.token) {
-        return false
-      }
-      setMetadata(presentation.metadata.Token, token ?? result.token)
-      setMetadataLocalStorage(login.metadata.LastToken, token ?? result.token)
+      setMetadata(presentation.metadata.Token, result.token)
+      setMetadataLocalStorage(login.metadata.LastToken, result.token)
       setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
       setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
 
@@ -108,16 +105,6 @@
     }
   }
 
-  const recoveryAction = {
-    caption: login.string.ForgotPassword,
-    i18n: login.string.Recover,
-    func: () => {
-      const loc = getCurrentLocation()
-      loc.path[1] = 'password'
-      loc.path.length = 2
-      navigate(loc)
-    }
-  }
   let loading = true
 
   async function chooseToken (time: number): Promise<void> {
@@ -125,15 +112,11 @@
       const lastToken = fetchMetadataLocalStorage(login.metadata.LastToken)
       if (lastToken != null) {
         try {
-          const info = await getAccount(false, lastToken)
+          const info = await getAccount(false)
           if (info !== undefined) {
-            await doLoginNavigate(
-              info,
-              (st) => {
-                status = st
-              },
-              lastToken
-            )
+            await doLoginNavigate(info, (st) => {
+              status = st
+            })
           }
         } catch (err: any) {
           setMetadataLocalStorage(login.metadata.LastToken, null)
diff --git a/plugins/login-resources/src/components/PasswordRequest.svelte b/plugins/login-resources/src/components/PasswordRequest.svelte
index 4e69042a60..8072bc16d8 100644
--- a/plugins/login-resources/src/components/PasswordRequest.svelte
+++ b/plugins/login-resources/src/components/PasswordRequest.svelte
@@ -16,10 +16,12 @@
   import { OK, Severity, Status } from '@hcengineering/platform'
   import { MessageBox } from '@hcengineering/presentation'
 
-  import { getCurrentLocation, navigate, showPopup } from '@hcengineering/ui'
+  import { showPopup } from '@hcengineering/ui'
   import login from '../plugin'
-  import { requestPassword } from '../utils'
+  import { goTo, requestPassword } from '../utils'
   import Form from './Form.svelte'
+  import { BottomAction } from '..'
+  import { signUpAction } from '../actions'
 
   const fields = [{ id: 'email', name: 'username', i18n: login.string.Email }]
 
@@ -46,35 +48,20 @@
           },
           undefined,
           () => {
-            const loc = getCurrentLocation()
-            loc.path[1] = 'login'
-            navigate(loc)
+            goTo('login')
           }
         )
       }
     }
   }
 
-  const signUpAction = {
-    caption: login.string.DoNotHaveAnAccount,
-    i18n: login.string.SignUp,
-    func: () => {
-      const loc = getCurrentLocation()
-      loc.path[1] = 'signup'
-      loc.path.length = 2
-      navigate(loc)
-    }
-  }
-
-  const bottomActions = [
+  const bottomActions: BottomAction[] = [
     {
       caption: login.string.KnowPassword,
       i18n: login.string.LogIn,
+      page: 'login',
       func: () => {
-        const loc = getCurrentLocation()
-        loc.path[1] = 'login'
-        loc.path.length = 2
-        navigate(loc)
+        goTo('login')
       }
     },
     signUpAction
diff --git a/plugins/login-resources/src/components/PasswordRestore.svelte b/plugins/login-resources/src/components/PasswordRestore.svelte
index afb0cab33e..bed8c838b1 100644
--- a/plugins/login-resources/src/components/PasswordRestore.svelte
+++ b/plugins/login-resources/src/components/PasswordRestore.svelte
@@ -15,11 +15,11 @@
 <script lang="ts">
   import { OK, setMetadata, Severity, Status } from '@hcengineering/platform'
 
-  import { getCurrentLocation, navigate, setMetadataLocalStorage } from '@hcengineering/ui'
-  import login from '../plugin'
-  import { restorePassword } from '../utils'
-  import Form from './Form.svelte'
   import presentation from '@hcengineering/presentation'
+  import { getCurrentLocation, setMetadataLocalStorage } from '@hcengineering/ui'
+  import login from '../plugin'
+  import { goTo, restorePassword } from '../utils'
+  import Form from './Form.svelte'
 
   const fields = [
     { id: 'new-password', name: 'password', i18n: login.string.Password, password: true },
@@ -48,10 +48,7 @@
         setMetadata(presentation.metadata.Token, result.token)
         setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
         setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
-        const loc = getCurrentLocation()
-        loc.path[1] = 'selectWorkspace'
-        loc.path.length = 2
-        navigate(loc)
+        goTo('selectWorkspace')
       }
     }
   }
diff --git a/plugins/login-resources/src/components/SelectWorkspace.svelte b/plugins/login-resources/src/components/SelectWorkspace.svelte
index c5f2b095df..12594f2920 100644
--- a/plugins/login-resources/src/components/SelectWorkspace.svelte
+++ b/plugins/login-resources/src/components/SelectWorkspace.svelte
@@ -14,22 +14,14 @@
 // limitations under the License.
 -->
 <script lang="ts">
-  import { OK, Severity, Status } from '@hcengineering/platform'
-  import presentation from '@hcengineering/presentation'
-  import {
-    Button,
-    Label,
-    Scroller,
-    deviceOptionsStore as deviceInfo,
-    getCurrentLocation,
-    navigate,
-    setMetadataLocalStorage
-  } from '@hcengineering/ui'
-  import login from '../plugin'
-  import { getAccount, getWorkspaces, navigateToWorkspace, selectWorkspace } from '../utils'
-  import StatusControl from './StatusControl.svelte'
   import { LoginInfo, Workspace } from '@hcengineering/login'
+  import { OK, Severity, Status } from '@hcengineering/platform'
+  import presentation, { NavLink } from '@hcengineering/presentation'
+  import { Button, Label, Scroller, deviceOptionsStore as deviceInfo, setMetadataLocalStorage } from '@hcengineering/ui'
   import { onMount } from 'svelte'
+  import login from '../plugin'
+  import { getAccount, getHref, getWorkspaces, goTo, navigateToWorkspace, selectWorkspace } from '../utils'
+  import StatusControl from './StatusControl.svelte'
 
   const CHECK_INTERVAL = 1000
 
@@ -41,11 +33,11 @@
 
   let account: LoginInfo | undefined = undefined
 
-  async function loadAccount () {
+  async function loadAccount (): Promise<void> {
     account = await getAccount()
   }
 
-  async function updateWorkspaces () {
+  async function updateWorkspaces (): Promise<void> {
     try {
       workspaces = await getWorkspaces()
     } catch (e) {
@@ -57,14 +49,14 @@
   }
 
   onMount(() => {
-    loadAccount()
+    void loadAccount()
 
     return () => {
       flagToUpdateWorkspaces = false
     }
   })
 
-  async function select (workspace: string) {
+  async function select (workspace: string): Promise<void> {
     status = new Status(Severity.INFO, login.status.ConnectingToServer, {})
 
     const [loginStatus, result] = await selectWorkspace(workspace)
@@ -73,42 +65,25 @@
     navigateToWorkspace(workspace, result, navigateUrl)
   }
 
-  async function _getWorkspaces () {
+  async function _getWorkspaces (): Promise<void> {
     try {
       const res = await getWorkspaces()
 
       if (res.length === 0 && account?.confirmed === false) {
-        const loc = getCurrentLocation()
-        loc.path[1] = 'confirmationSend'
-        loc.path.length = 2
-        navigate(loc)
+        goTo('confirmationSend')
       }
 
       workspaces = res
       flagToUpdateWorkspaces = true
-      updateWorkspaces()
+      await updateWorkspaces()
     } catch (err: any) {
       setMetadataLocalStorage(presentation.metadata.Token, null)
       setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
       setMetadataLocalStorage(login.metadata.LoginEmail, null)
-      changeAccount()
+      goTo('login')
       throw err
     }
   }
-
-  function createWorkspace (): void {
-    const loc = getCurrentLocation()
-    loc.path[1] = 'createWorkspace'
-    loc.path.length = 2
-    navigate(loc)
-  }
-
-  function changeAccount (): void {
-    const loc = getCurrentLocation()
-    loc.path[1] = 'login'
-    loc.path.length = 2
-    navigate(loc)
-  }
 </script>
 
 <form class="container" style:padding={$deviceInfo.docWidth <= 480 ? '1.25rem' : '5rem'}>
@@ -134,7 +109,14 @@
         {/each}
         {#if workspaces.length === 0 && account?.confirmed === true}
           <div class="form-row send">
-            <Button label={login.string.CreateWorkspace} kind={'primary'} width="100%" on:click={createWorkspace} />
+            <Button
+              label={login.string.CreateWorkspace}
+              kind={'primary'}
+              width="100%"
+              on:click={() => {
+                goTo('createWorkspace')
+              }}
+            />
           </div>
         {/if}
       </div>
@@ -144,12 +126,12 @@
       {#if workspaces.length}
         <div>
           <span><Label label={login.string.WantAnotherWorkspace} /></span>
-          <a href="." on:click|preventDefault={createWorkspace}><Label label={login.string.CreateWorkspace} /></a>
+          <NavLink href={getHref('createWorkspace')}><Label label={login.string.CreateWorkspace} /></NavLink>
         </div>
       {/if}
       <div>
         <span><Label label={login.string.NotSeeingWorkspace} /></span>
-        <a href="." on:click|preventDefault={changeAccount}><Label label={login.string.ChangeAccount} /></a>
+        <NavLink href={getHref('login')}><Label label={login.string.ChangeAccount} /></NavLink>
       </div>
     </div>
   {/await}
diff --git a/plugins/login-resources/src/components/SignupForm.svelte b/plugins/login-resources/src/components/SignupForm.svelte
index f91d0bd3a2..3be00e5918 100644
--- a/plugins/login-resources/src/components/SignupForm.svelte
+++ b/plugins/login-resources/src/components/SignupForm.svelte
@@ -16,9 +16,9 @@
 <script lang="ts">
   import { OK, Severity, Status, setMetadata } from '@hcengineering/platform'
   import presentation from '@hcengineering/presentation'
-  import { getCurrentLocation, navigate, setMetadataLocalStorage } from '@hcengineering/ui'
+  import { setMetadataLocalStorage } from '@hcengineering/ui'
   import login from '../plugin'
-  import { signUp } from '../utils'
+  import { goTo, signUp } from '../utils'
   import Form from './Form.svelte'
 
   const fields = [
@@ -51,10 +51,7 @@
       if (result !== undefined) {
         setMetadata(presentation.metadata.Token, result.token)
         setMetadataLocalStorage(login.metadata.LastToken, result.token)
-        const loc = getCurrentLocation()
-        loc.path[1] = 'confirmationSend'
-        loc.path.length = 2
-        navigate(loc)
+        goTo('confirmationSend')
       }
     }
   }
diff --git a/plugins/login-resources/src/index.ts b/plugins/login-resources/src/index.ts
index df59f701f9..dc12877c9f 100644
--- a/plugins/login-resources/src/index.ts
+++ b/plugins/login-resources/src/index.ts
@@ -14,6 +14,7 @@
 // limitations under the License.
 //
 
+import { type IntlString } from '@hcengineering/platform'
 import InviteLink from './components/InviteLink.svelte'
 import LoginApp from './components/LoginApp.svelte'
 import { changePassword, getWorkspaces, leaveWorkspace, selectWorkspace, sendInvite } from './utils'
@@ -38,4 +39,25 @@ export default async () => ({
   }
 })
 
+export const pages = [
+  'login',
+  'signup',
+  'createWorkspace',
+  'password',
+  'recovery',
+  'selectWorkspace',
+  'join',
+  'confirm',
+  'confirmationSend'
+] as const
+
+export type Pages = (typeof pages)[number]
+
+export interface BottomAction {
+  i18n: IntlString
+  page?: Pages
+  func: () => void
+  caption: IntlString
+}
+
 export * from './utils'
diff --git a/plugins/login-resources/src/utils.ts b/plugins/login-resources/src/utils.ts
index 47a685a651..2c9049a50b 100644
--- a/plugins/login-resources/src/utils.ts
+++ b/plugins/login-resources/src/utils.ts
@@ -20,21 +20,23 @@ import {
   PlatformError,
   getMetadata,
   setMetadata,
+  translate,
   unknownError,
   unknownStatus,
-  type Status,
-  translate
+  type Status
 } from '@hcengineering/platform'
 import presentation from '@hcengineering/presentation'
 import {
   fetchMetadataLocalStorage,
   getCurrentLocation,
   locationStorageKeyId,
+  locationToUrl,
   navigate,
   setMetadataLocalStorage,
   type Location
 } from '@hcengineering/ui'
 import { workbenchId } from '@hcengineering/workbench'
+import { type Pages } from './index'
 
 const DEV_WORKSPACE = 'DEV WORKSPACE'
 
@@ -239,7 +241,7 @@ export async function getWorkspaces (): Promise<Workspace[]> {
   }
 }
 
-export async function getAccount (doNavigate: boolean = true, token?: string): Promise<LoginInfo | undefined> {
+export async function getAccount (doNavigate: boolean = true): Promise<LoginInfo | undefined> {
   const accountsUrl = getMetadata(login.metadata.AccountsUrl)
 
   if (accountsUrl === undefined) {
@@ -255,7 +257,7 @@ export async function getAccount (doNavigate: boolean = true, token?: string): P
     }
   }
 
-  token = token ?? getMetadata(presentation.metadata.Token)
+  const token = getMetadata(presentation.metadata.Token) ?? fetchMetadataLocalStorage(login.metadata.LastToken)
   if (token === undefined) {
     if (doNavigate) {
       const loc = getCurrentLocation()
@@ -770,3 +772,47 @@ async function handleStatusError (message: string, err: Status): Promise<void> {
   const label = await translate(err.code, err.params, 'en')
   Analytics.handleError(new Error(`${message}: ${label}`))
 }
+
+export function getLoc (path: Pages): Location {
+  const loc = getCurrentLocation()
+  loc.path[1] = path
+  loc.path.length = 2
+  return loc
+}
+
+export function goTo (path: Pages, clearQuery: boolean = false): void {
+  const loc = getLoc(path)
+  if (clearQuery) {
+    loc.query = undefined
+  }
+  navigate(loc)
+}
+
+export function getHref (path: Pages): string {
+  const url = locationToUrl(getLoc(path))
+  const frontUrl = getMetadata(presentation.metadata.FrontUrl)
+  const host = frontUrl ?? document.location.origin
+  return host + url
+}
+
+export async function afterConfirm (): Promise<void> {
+  const joinedWS = await getWorkspaces()
+  if (joinedWS.length === 0) {
+    goTo('createWorkspace')
+  } else if (joinedWS.length === 1) {
+    const result = (await selectWorkspace(joinedWS[0].workspace))[1]
+    if (result !== undefined) {
+      setMetadata(presentation.metadata.Token, result.token)
+      setMetadataLocalStorage(login.metadata.LastToken, result.token)
+      setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
+      setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
+      const tokens: Record<string, string> = fetchMetadataLocalStorage(login.metadata.LoginTokens) ?? {}
+      tokens[result.workspace] = result.token
+      setMetadataLocalStorage(login.metadata.LoginTokens, tokens)
+
+      navigateToWorkspace(joinedWS[0].workspace, result)
+    }
+  } else {
+    goTo('selectWorkspace')
+  }
+}
diff --git a/plugins/workbench-resources/src/components/SelectWorkspaceMenu.svelte b/plugins/workbench-resources/src/components/SelectWorkspaceMenu.svelte
index 3a36a7fa75..51ede1f4ee 100644
--- a/plugins/workbench-resources/src/components/SelectWorkspaceMenu.svelte
+++ b/plugins/workbench-resources/src/components/SelectWorkspaceMenu.svelte
@@ -61,6 +61,7 @@
           if (loginInfo !== undefined) {
             tokens[ws] = loginInfo?.token
           }
+          setMetadataLocalStorage(login.metadata.LoginTokens, tokens)
         }
         const last = localStorage.getItem(`${locationStorageKeyId}_${ws}`)
         if (last !== null) {
diff --git a/server/account/src/index.ts b/server/account/src/index.ts
index ea8192c88d..b243eb71f9 100644
--- a/server/account/src/index.ts
+++ b/server/account/src/index.ts
@@ -787,7 +787,7 @@ export const createUserWorkspace =
       if (info === null) {
         throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, { account: email }))
       }
-      if (info.confirmed !== true) {
+      if (info.confirmed === false) {
         throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotConfirmed, { account: email }))
       }
 
diff --git a/tests/sanity/tests/model/select-workspace-page.ts b/tests/sanity/tests/model/select-workspace-page.ts
index 51aaab4303..c81bfa31bf 100644
--- a/tests/sanity/tests/model/select-workspace-page.ts
+++ b/tests/sanity/tests/model/select-workspace-page.ts
@@ -22,6 +22,7 @@ export class SelectWorkspacePage extends CommonPage {
   }
 
   async createWorkspace (workspaceName: string): Promise<void> {
+    await this.buttonCreateWorkspace.waitFor({ state: 'visible' })
     await this.buttonWorkspaceName.fill(workspaceName)
     expect(await this.buttonCreateNewWorkspace.isEnabled()).toBe(true)
     await this.buttonCreateNewWorkspace.click()
diff --git a/tests/sanity/tests/workspace/create.spec.ts b/tests/sanity/tests/workspace/create.spec.ts
index 9aab9638ae..9070fbfa66 100644
--- a/tests/sanity/tests/workspace/create.spec.ts
+++ b/tests/sanity/tests/workspace/create.spec.ts
@@ -29,7 +29,6 @@ test.describe('Workspace tests', () => {
     await signUpPage.signUp(newUser)
 
     const selectWorkspacePage = new SelectWorkspacePage(page)
-    await selectWorkspacePage.buttonCreateWorkspace.click()
     await selectWorkspacePage.createWorkspace(newWorkspaceName)
 
     const leftSideMenuPage = new LeftSideMenuPage(page)
@@ -67,7 +66,6 @@ test.describe('Workspace tests', () => {
     await signUpPage.signUp(newUser)
 
     const selectWorkspacePage = new SelectWorkspacePage(page)
-    await selectWorkspacePage.buttonCreateWorkspace.click()
     await selectWorkspacePage.createWorkspace(newWorkspaceName)
 
     const leftSideMenuPage = new LeftSideMenuPage(page)
@@ -118,7 +116,6 @@ test.describe('Workspace tests', () => {
     await signUpPage.buttonSignUp.click()
 
     const selectWorkspacePage = new SelectWorkspacePage(page)
-    await selectWorkspacePage.buttonCreateWorkspace.click()
     await selectWorkspacePage.checkInfo(page, 'Required field Workspace name')
     await selectWorkspacePage.buttonWorkspaceName.fill(newWorkspaceName)
     await selectWorkspacePage.checkInfoSectionNotExist(page)
@@ -141,7 +138,6 @@ test.describe('Workspace tests', () => {
     await signUpPage.signUp(newUser)
 
     const selectWorkspacePage = new SelectWorkspacePage(page)
-    await selectWorkspacePage.buttonCreateWorkspace.click()
     await selectWorkspacePage.createWorkspace(newWorkspaceName)
 
     const leftSideMenuPage = new LeftSideMenuPage(page)
@@ -191,7 +187,6 @@ test.describe('Workspace tests', () => {
     await signUpPage.signUp(newUser)
 
     const selectWorkspacePage = new SelectWorkspacePage(page)
-    await selectWorkspacePage.buttonCreateWorkspace.click()
     await selectWorkspacePage.createWorkspace(newWorkspaceName)
 
     const leftSideMenuPage = new LeftSideMenuPage(page)