From 6d4cfe7316d9effc873c9385b6d1090c32b986d9 Mon Sep 17 00:00:00 2001
From: "Kilu.He" <108015703+qinluhe@users.noreply.github.com>
Date: Fri, 15 Mar 2024 20:29:00 +0800
Subject: [PATCH] feat: support-OAuth-login (#4899)
* feat: support-OAuth-login
* fix: modified ubuntu version and devtool
* fix: ts lint error
---
.github/workflows/tauri_ci.yaml | 9 +-
.github/workflows/tauri_release.yml | 10 +-
frontend/appflowy_tauri/.gitignore | 4 +-
frontend/appflowy_tauri/src-tauri/.gitignore | 2 +-
frontend/appflowy_tauri/src-tauri/Cargo.lock | 92 +++++++++
frontend/appflowy_tauri/src-tauri/Cargo.toml | 3 +
frontend/appflowy_tauri/src-tauri/Info.plist | 19 ++
.../appflowy_tauri/src-tauri/env.development | 4 +
.../appflowy_tauri/src-tauri/env.production | 4 +
frontend/appflowy_tauri/src-tauri/src/init.rs | 28 ++-
frontend/appflowy_tauri/src-tauri/src/main.rs | 23 ++-
.../appflowy_tauri/src-tauri/tauri.conf.json | 2 +-
.../src/appflowy_app/AppMain.hooks.ts | 7 +-
.../src/appflowy_app/AppMain.tsx | 2 +
.../application/folder/workspace.service.ts | 26 +--
.../application/user/auth.service.ts | 74 ++++++--
.../_shared/devtool/AppFlowyDevTool.tsx | 61 ++++++
.../_shared/devtool/ManualSignInDialog.tsx | 114 ++++++++++++
.../_shared/login/LoginButtonGroup.tsx | 26 ---
.../components/_shared/login/index.ts | 1 -
.../components/auth/LoginButtonGroup.tsx | 51 +++++
.../components/auth/ProtectedRoutes.tsx | 100 ++++++++--
.../appflowy_app/components/auth/Welcome.tsx | 2 +-
.../components/auth/auth.hooks.ts | 174 ++++++++++++------
.../workspace_manager/Workspace.hooks.ts | 24 +--
.../layout/workspace_manager/Workspace.tsx | 16 +-
.../workspace_manager/WorkspaceManager.tsx | 14 +-
.../components/settings/Login.tsx | 2 +-
.../components/settings/SettingsDialog.tsx | 29 ++-
.../settings/my_account/AccountLogin.tsx | 15 +-
.../settings/workplace/WorkplaceDisplay.tsx | 53 ++++--
.../stores/reducers/current-user/slice.ts | 24 ++-
.../stores/reducers/workspace/slice.ts | 22 ++-
.../src/appflowy_app/utils/color.ts | 21 ++-
.../flowy-user/src/user_manager/manager.rs | 1 +
35 files changed, 861 insertions(+), 198 deletions(-)
create mode 100644 frontend/appflowy_tauri/src-tauri/Info.plist
create mode 100644 frontend/appflowy_tauri/src-tauri/env.development
create mode 100644 frontend/appflowy_tauri/src-tauri/env.production
create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/_shared/devtool/AppFlowyDevTool.tsx
create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/_shared/devtool/ManualSignInDialog.tsx
delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/_shared/login/LoginButtonGroup.tsx
delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/_shared/login/index.ts
create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/auth/LoginButtonGroup.tsx
diff --git a/.github/workflows/tauri_ci.yaml b/.github/workflows/tauri_ci.yaml
index 8d99091aab..462bebb8dd 100644
--- a/.github/workflows/tauri_ci.yaml
+++ b/.github/workflows/tauri_ci.yaml
@@ -22,7 +22,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- platform: [ubuntu-latest]
+ platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
@@ -32,7 +32,7 @@ jobs:
- uses: actions/checkout@v4
- name: Maximize build space (ubuntu only)
- if: matrix.platform == 'ubuntu-latest'
+ if: matrix.platform == 'ubuntu-20.04'
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
@@ -80,7 +80,7 @@ jobs:
vcpkg integrate install
- name: install dependencies (ubuntu only)
- if: matrix.platform == 'ubuntu-latest'
+ if: matrix.platform == 'ubuntu-20.04'
working-directory: frontend
run: |
sudo apt-get update
@@ -110,4 +110,5 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tauriScript: pnpm tauri
- projectPath: frontend/appflowy_tauri
\ No newline at end of file
+ projectPath: frontend/appflowy_tauri
+ args: "--debug"
\ No newline at end of file
diff --git a/.github/workflows/tauri_release.yml b/.github/workflows/tauri_release.yml
index e031e65ccd..2e4be46dbe 100644
--- a/.github/workflows/tauri_release.yml
+++ b/.github/workflows/tauri_release.yml
@@ -31,7 +31,7 @@ jobs:
- platform: macos-latest
args: "--target x86_64-apple-darwin"
target: "macos-x86_64"
- - platform: ubuntu-latest
+ - platform: ubuntu-20.04
args: "--target x86_64-unknown-linux-gnu"
target: "linux-x86_64"
@@ -46,7 +46,7 @@ jobs:
ref: ${{ github.event.inputs.branch }}
- name: Maximize build space (ubuntu only)
- if: matrix.settings.platform == 'ubuntu-latest'
+ if: matrix.settings.platform == 'ubuntu-20.04'
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
@@ -88,7 +88,7 @@ jobs:
vcpkg integrate install
- name: install dependencies (ubuntu only)
- if: matrix.settings.platform == 'ubuntu-latest'
+ if: matrix.settings.platform == 'ubuntu-20.04'
working-directory: frontend
run: |
sudo apt-get update
@@ -140,14 +140,14 @@ jobs:
- name: Upload Deb package(ubuntu only)
uses: actions/upload-artifact@v4
- if: matrix.settings.platform == 'ubuntu-latest'
+ if: matrix.settings.platform == 'ubuntu-20.04'
with:
name: ${{ env.PACKAGE_PREFIX }}.deb
path: frontend/appflowy_tauri/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/app-flowy_${{ github.event.inputs.version }}_amd64.deb
- name: Upload AppImage package(ubuntu only)
uses: actions/upload-artifact@v4
- if: matrix.settings.platform == 'ubuntu-latest'
+ if: matrix.settings.platform == 'ubuntu-20.04'
with:
name: ${{ env.PACKAGE_PREFIX }}.AppImage
path: frontend/appflowy_tauri/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/app-flowy_${{ github.event.inputs.version }}_amd64.AppImage
diff --git a/frontend/appflowy_tauri/.gitignore b/frontend/appflowy_tauri/.gitignore
index 6a6338d33e..32a3d59bc2 100644
--- a/frontend/appflowy_tauri/.gitignore
+++ b/frontend/appflowy_tauri/.gitignore
@@ -28,4 +28,6 @@ dist-ssr
**/src/appflowy_app/i18n/translations/
coverage
-**/AppFlowy-Collab
\ No newline at end of file
+**/AppFlowy-Collab
+
+.env
\ No newline at end of file
diff --git a/frontend/appflowy_tauri/src-tauri/.gitignore b/frontend/appflowy_tauri/src-tauri/.gitignore
index f4dfb82b2c..61e1bdd46a 100644
--- a/frontend/appflowy_tauri/src-tauri/.gitignore
+++ b/frontend/appflowy_tauri/src-tauri/.gitignore
@@ -1,4 +1,4 @@
# Generated by Cargo
# will have compiled files and executables
/target/
-
+.env
diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock
index 996812c1bb..e270df39a2 100644
--- a/frontend/appflowy_tauri/src-tauri/Cargo.lock
+++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock
@@ -182,6 +182,7 @@ name = "appflowy_tauri"
version = "0.0.0"
dependencies = [
"bytes",
+ "dotenv",
"flowy-config",
"flowy-core",
"flowy-date",
@@ -194,6 +195,7 @@ dependencies = [
"serde_json",
"tauri",
"tauri-build",
+ "tauri-plugin-deep-link",
"tauri-utils",
"tracing",
"uuid",
@@ -1439,6 +1441,15 @@ dependencies = [
"subtle",
]
+[[package]]
+name = "dirs"
+version = "5.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
+dependencies = [
+ "dirs-sys",
+]
+
[[package]]
name = "dirs-next"
version = "2.0.0"
@@ -1449,6 +1460,18 @@ dependencies = [
"dirs-sys-next",
]
+[[package]]
+name = "dirs-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
@@ -1466,6 +1489,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
+[[package]]
+name = "dotenv"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
+
[[package]]
name = "dtoa"
version = "1.0.6"
@@ -3088,6 +3117,19 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "interprocess"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81f2533f3be42fffe3b5e63b71aeca416c1c3bc33e4e27be018521e76b1f38fb"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "rustc_version",
+ "to_method",
+ "winapi",
+]
+
[[package]]
name = "ipnet"
version = "2.8.0"
@@ -3831,6 +3873,28 @@ dependencies = [
"objc_id",
]
+[[package]]
+name = "objc-sys"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c71324e4180d0899963fc83d9d241ac39e699609fc1025a850aadac8257459"
+
+[[package]]
+name = "objc2"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d"
+dependencies = [
+ "objc-sys",
+ "objc2-encode",
+]
+
+[[package]]
+name = "objc2-encode"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666"
+
[[package]]
name = "objc_exception"
version = "0.1.2"
@@ -3934,6 +3998,12 @@ dependencies = [
"vcpkg",
]
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
[[package]]
name = "os_pipe"
version = "0.9.2"
@@ -6016,6 +6086,22 @@ dependencies = [
"tauri-utils",
]
+[[package]]
+name = "tauri-plugin-deep-link"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4536f5f6602e8fdfaa7b3b185076c2a0704f8eb7015f4e58461eb483ec3ed1f8"
+dependencies = [
+ "dirs",
+ "interprocess",
+ "log",
+ "objc2",
+ "once_cell",
+ "tauri-utils",
+ "windows-sys 0.48.0",
+ "winreg 0.50.0",
+]
+
[[package]]
name = "tauri-runtime"
version = "0.14.1"
@@ -6242,6 +6328,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+[[package]]
+name = "to_method"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8"
+
[[package]]
name = "tokio"
version = "1.36.0"
diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml
index b3075f75ff..8682201a73 100644
--- a/frontend/appflowy_tauri/src-tauri/Cargo.toml
+++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml
@@ -67,7 +67,10 @@ flowy-document = { path = "../../rust-lib/flowy-document", features = [
flowy-notification = { path = "../../rust-lib/flowy-notification", features = [
"tauri_ts",
] }
+
uuid = "1.5.0"
+tauri-plugin-deep-link = "0.1.2"
+dotenv = "0.15.0"
[features]
# by default Tauri runs in production mode
diff --git a/frontend/appflowy_tauri/src-tauri/Info.plist b/frontend/appflowy_tauri/src-tauri/Info.plist
new file mode 100644
index 0000000000..25b430c049
--- /dev/null
+++ b/frontend/appflowy_tauri/src-tauri/Info.plist
@@ -0,0 +1,19 @@
+
+
+
+
+
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+
+ appflowy-flutter
+ CFBundleURLSchemes
+
+ appflowy-flutter
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/appflowy_tauri/src-tauri/env.development b/frontend/appflowy_tauri/src-tauri/env.development
new file mode 100644
index 0000000000..188835e3d0
--- /dev/null
+++ b/frontend/appflowy_tauri/src-tauri/env.development
@@ -0,0 +1,4 @@
+APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_BASE_URL=https://test.appflowy.cloud
+APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_WS_BASE_URL=wss://test.appflowy.cloud/ws/v1
+APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_GOTRUE_URL=https://test.appflowy.cloud/gotrue
+APPFLOWY_CLOUD_ENV_CLOUD_TYPE=2
diff --git a/frontend/appflowy_tauri/src-tauri/env.production b/frontend/appflowy_tauri/src-tauri/env.production
new file mode 100644
index 0000000000..b03c328b84
--- /dev/null
+++ b/frontend/appflowy_tauri/src-tauri/env.production
@@ -0,0 +1,4 @@
+APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_BASE_URL=https://beta.appflowy.cloud
+APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_WS_BASE_URL=wss://beta.appflowy.cloud/ws/v1
+APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_GOTRUE_URL=https://beta.appflowy.cloud/gotrue
+APPFLOWY_CLOUD_ENV_CLOUD_TYPE=2
diff --git a/frontend/appflowy_tauri/src-tauri/src/init.rs b/frontend/appflowy_tauri/src-tauri/src/init.rs
index 7f7c2726d3..40c0e5d47b 100644
--- a/frontend/appflowy_tauri/src-tauri/src/init.rs
+++ b/frontend/appflowy_tauri/src-tauri/src/init.rs
@@ -3,10 +3,33 @@ use flowy_core::{AppFlowyCore, DEFAULT_NAME};
use lib_dispatch::runtime::AFPluginRuntime;
use std::sync::Arc;
+use dotenv::dotenv;
+
+pub fn read_env() {
+ dotenv().ok();
+
+ let env = if cfg!(debug_assertions) {
+ include_str!("../env.development")
+ } else {
+ include_str!("../env.production")
+ };
+
+ for line in env.lines() {
+ if let Some((key, value)) = line.split_once('=') {
+ // Check if the environment variable is not already set in the system
+ let current_value = std::env::var(key).unwrap_or_default();
+ if current_value.is_empty() {
+ std::env::set_var(key, value);
+ }
+ }
+ }
+}
+
pub fn init_flowy_core() -> AppFlowyCore {
let config_json = include_str!("../tauri.conf.json");
let config: tauri_utils::config::Config = serde_json::from_str(config_json).unwrap();
+ let app_version = config.package.version.clone().map(|v| v.to_string()).unwrap_or_else(|| "0.0.0".to_string());
let mut data_path = tauri::api::path::app_local_data_dir(&config).unwrap();
if cfg!(debug_assertions) {
data_path.push("data_dev");
@@ -18,10 +41,11 @@ pub fn init_flowy_core() -> AppFlowyCore {
let application_path = data_path.to_str().unwrap().to_string();
let device_id = uuid::Uuid::new_v4().to_string();
+ read_env();
std::env::set_var("RUST_LOG", "trace");
- // TODO(nathan): pass the real version here
+
let config = AppFlowyCoreConfig::new(
- "1.0.0".to_string(),
+ app_version,
custom_application_path,
application_path,
device_id,
diff --git a/frontend/appflowy_tauri/src-tauri/src/main.rs b/frontend/appflowy_tauri/src-tauri/src/main.rs
index 10a17b5a3a..6a69de07fd 100644
--- a/frontend/appflowy_tauri/src-tauri/src/main.rs
+++ b/frontend/appflowy_tauri/src-tauri/src/main.rs
@@ -3,6 +3,10 @@
windows_subsystem = "windows"
)]
+#[allow(dead_code)]
+pub const DEEP_LINK_SCHEME: &str = "appflowy-flutter";
+pub const OPEN_DEEP_LINK: &str = "open_deep_link";
+
mod init;
mod notification;
mod request;
@@ -12,8 +16,11 @@ use init::*;
use notification::*;
use request::*;
use tauri::Manager;
+extern crate dotenv;
fn main() {
+ tauri_plugin_deep_link::prepare(DEEP_LINK_SCHEME);
+
let flowy_core = init_flowy_core();
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![invoke_request])
@@ -26,6 +33,7 @@ fn main() {
unregister_all_notification_sender();
register_notification_sender(TSNotificationSender::new(app_handler.clone()));
// tauri::async_runtime::spawn(async move {});
+
window.listen_global(AF_EVENT, move |event| {
on_event(app_handler.clone(), event);
});
@@ -33,18 +41,29 @@ fn main() {
.setup(|_app| {
let splashscreen_window = _app.get_window("splashscreen").unwrap();
let window = _app.get_window("main").unwrap();
+ let handle = _app.handle();
// we perform the initialization code on a new task so the app doesn't freeze
tauri::async_runtime::spawn(async move {
// initialize your app here instead of sleeping :)
- println!("Initializing...");
std::thread::sleep(std::time::Duration::from_secs(2));
- println!("Done initializing.");
// After it's done, close the splashscreen and display the main window
splashscreen_window.close().unwrap();
window.show().unwrap();
+ // If you need macOS support this must be called in .setup() !
+ // Otherwise this could be called right after prepare() but then you don't have access to tauri APIs
+ // On macOS You still have to install a .app bundle you got from tauri build --debug for this to work!
+ tauri_plugin_deep_link::register(
+ DEEP_LINK_SCHEME,
+ move |request| {
+ dbg!(&request);
+ handle.emit_all(OPEN_DEEP_LINK, request).unwrap();
+ },
+ )
+ .unwrap(/* If listening to the scheme is optional for your app, you don't want to unwrap here. */);
});
+
Ok(())
})
.run(tauri::generate_context!())
diff --git a/frontend/appflowy_tauri/src-tauri/tauri.conf.json b/frontend/appflowy_tauri/src-tauri/tauri.conf.json
index 5011422312..11dd7c206c 100644
--- a/frontend/appflowy_tauri/src-tauri/tauri.conf.json
+++ b/frontend/appflowy_tauri/src-tauri/tauri.conf.json
@@ -8,7 +8,7 @@
},
"package": {
"productName": "AppFlowy",
- "version": "0.0.0"
+ "version": "0.0.1"
},
"tauri": {
"allowlist": {
diff --git a/frontend/appflowy_tauri/src/appflowy_app/AppMain.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/AppMain.hooks.ts
index 48c8194d27..9c46b8ab38 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/AppMain.hooks.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/AppMain.hooks.ts
@@ -1,6 +1,6 @@
import { useAppDispatch, useAppSelector } from '$app/stores/store';
import { useEffect, useMemo } from 'react';
-import { currentUserActions } from '$app_reducers/current-user/slice';
+import { currentUserActions, LoginState } from '$app_reducers/current-user/slice';
import { Theme as ThemeType, ThemeMode } from '$app/stores/reducers/current-user/slice';
import { createTheme } from '@mui/material/styles';
import { getDesignTokens } from '$app/utils/mui';
@@ -10,6 +10,8 @@ import { UserService } from '$app/application/user/user.service';
export function useUserSetting() {
const dispatch = useAppDispatch();
const { i18n } = useTranslation();
+ const loginState = useAppSelector((state) => state.currentUser.loginState);
+
const { themeMode = ThemeMode.System, theme: themeType = ThemeType.Default } = useAppSelector((state) => {
return {
themeMode: state.currentUser.userSetting.themeMode,
@@ -22,6 +24,7 @@ export function useUserSetting() {
(themeMode === ThemeMode.System && window.matchMedia('(prefers-color-scheme: dark)').matches);
useEffect(() => {
+ if (loginState !== LoginState.Success && loginState !== undefined) return;
void (async () => {
const settings = await UserService.getAppearanceSetting();
@@ -29,7 +32,7 @@ export function useUserSetting() {
dispatch(currentUserActions.setUserSetting(settings));
await i18n.changeLanguage(settings.language);
})();
- }, [dispatch, i18n]);
+ }, [dispatch, i18n, loginState]);
useEffect(() => {
const html = document.documentElement;
diff --git a/frontend/appflowy_tauri/src/appflowy_app/AppMain.tsx b/frontend/appflowy_tauri/src/appflowy_app/AppMain.tsx
index b9fd53130a..76bdb167b0 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/AppMain.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/AppMain.tsx
@@ -8,6 +8,7 @@ import { useUserSetting } from '$app/AppMain.hooks';
import TrashPage from '$app/views/TrashPage';
import DocumentPage from '$app/views/DocumentPage';
import { Toaster } from 'react-hot-toast';
+import AppFlowyDevTool from '$app/components/_shared/devtool/AppFlowyDevTool';
function AppMain() {
const { muiTheme } = useUserSetting();
@@ -22,6 +23,7 @@ function AppMain() {
+ {process.env.NODE_ENV === 'development' && }
);
}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/folder/workspace.service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/folder/workspace.service.ts
index e58afb9f58..e6f28766f2 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/application/folder/workspace.service.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/application/folder/workspace.service.ts
@@ -9,6 +9,7 @@ import {
UserEventOpenWorkspace,
UserEventRenameWorkspace,
UserEventChangeWorkspaceIcon,
+ UserEventGetAllWorkspace,
} from '@/services/backend/events/flowy-user';
import {
FolderEventCreateView,
@@ -62,17 +63,13 @@ export async function getWorkspaceChildViews(id: string) {
}
export async function getWorkspaces() {
- const result = await FolderEventReadCurrentWorkspace();
+ const result = await UserEventGetAllWorkspace();
if (result.ok) {
- const item = result.val;
-
- return [
- {
- id: item.id,
- name: item.name,
- },
- ];
+ return result.val.items.map((workspace) => ({
+ id: workspace.workspace_id,
+ name: workspace.name,
+ }));
}
return [];
@@ -92,12 +89,7 @@ export async function getCurrentWorkspace() {
const result = await FolderEventReadCurrentWorkspace();
if (result.ok) {
- const workspace = result.val;
-
- return {
- id: workspace.id,
- name: workspace.name,
- };
+ return result.val.id;
}
return null;
@@ -111,9 +103,7 @@ export async function createCurrentWorkspaceChildView(
const result = await FolderEventCreateView(payload);
if (result.ok) {
- const view = result.val;
-
- return view;
+ return result.val;
}
return Promise.reject(result.err);
diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/user/auth.service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/user/auth.service.ts
index 82c0a6779b..ec258abc87 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/application/user/auth.service.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/application/user/auth.service.ts
@@ -1,33 +1,63 @@
-import { SignInPayloadPB, SignUpPayloadPB } from '@/services/backend';
import {
- UserEventSignInWithEmailPassword,
+ SignUpPayloadPB,
+ OauthProviderPB,
+ ProviderTypePB,
+ OauthSignInPB,
+ AuthenticatorPB,
+ SignInPayloadPB,
+} from '@/services/backend';
+import {
UserEventSignOut,
UserEventSignUp,
+ UserEventGetOauthURLWithProvider,
+ UserEventOauthSignIn,
+ UserEventSignInWithEmailPassword,
} from '@/services/backend/events/flowy-user';
-import { nanoid } from '@reduxjs/toolkit';
import { Log } from '$app/utils/log';
export const AuthService = {
- signIn: async (params: { email: string; password: string }) => {
- const payload = SignInPayloadPB.fromObject({ email: params.email, password: params.password });
+ getOAuthURL: async (provider: ProviderTypePB) => {
+ const providerDataRes = await UserEventGetOauthURLWithProvider(
+ OauthProviderPB.fromObject({
+ provider,
+ })
+ );
- const res = await UserEventSignInWithEmailPassword(payload);
-
- if (res.ok) {
- return res.val;
+ if (!providerDataRes.ok) {
+ Log.error(providerDataRes.val.msg);
+ throw new Error(providerDataRes.val.msg);
}
- Log.error(res.val.msg);
- throw new Error(res.val.msg);
+ const providerData = providerDataRes.val;
+
+ return providerData.oauth_url;
},
- signUp: async (params: { name: string; email: string; password: string }) => {
- const deviceId = nanoid(8);
+ signInWithOAuth: async ({ uri, deviceId }: { uri: string; deviceId: string }) => {
+ const payload = OauthSignInPB.fromObject({
+ authenticator: AuthenticatorPB.AppFlowyCloud,
+ map: {
+ sign_in_url: uri,
+ device_id: deviceId,
+ },
+ });
+
+ const res = await UserEventOauthSignIn(payload);
+
+ if (!res.ok) {
+ Log.error(res.val.msg);
+ throw new Error(res.val.msg);
+ }
+
+ return res.val;
+ },
+
+ signUp: async (params: { deviceId: string; name: string; email: string; password: string }) => {
const payload = SignUpPayloadPB.fromObject({
name: params.name,
email: params.email,
password: params.password,
- device_id: deviceId,
+ device_id: params.deviceId,
});
const res = await UserEventSignUp(payload);
@@ -43,4 +73,20 @@ export const AuthService = {
signOut: () => {
return UserEventSignOut();
},
+
+ signIn: async (email: string, password: string) => {
+ const payload = SignInPayloadPB.fromObject({
+ email,
+ password,
+ });
+
+ const res = await UserEventSignInWithEmailPassword(payload);
+
+ if (!res.ok) {
+ Log.error(res.val.msg);
+ throw new Error(res.val.msg);
+ }
+
+ return res.val;
+ },
};
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/devtool/AppFlowyDevTool.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/devtool/AppFlowyDevTool.tsx
new file mode 100644
index 0000000000..5d3ed1e3de
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/devtool/AppFlowyDevTool.tsx
@@ -0,0 +1,61 @@
+import * as React from 'react';
+import SpeedDial from '@mui/material/SpeedDial';
+import SpeedDialIcon from '@mui/material/SpeedDialIcon';
+import SpeedDialAction from '@mui/material/SpeedDialAction';
+import { useMemo } from 'react';
+import { CloseOutlined, BuildOutlined, LoginOutlined, VisibilityOff } from '@mui/icons-material';
+import ManualSignInDialog from '$app/components/_shared/devtool/ManualSignInDialog';
+import { Portal } from '@mui/material';
+
+function AppFlowyDevTool() {
+ const [openManualSignIn, setOpenManualSignIn] = React.useState(false);
+ const [hidden, setHidden] = React.useState(false);
+ const actions = useMemo(
+ () => [
+ {
+ icon: ,
+ name: 'Manual SignIn',
+ onClick: () => {
+ setOpenManualSignIn(true);
+ },
+ },
+ {
+ icon: ,
+ name: 'Hide Dev Tool',
+ onClick: () => {
+ setHidden(true);
+ },
+ },
+ ],
+ []
+ );
+
+ return (
+
+ } icon={} />}
+ >
+ {actions.map((action) => (
+
+ ))}
+
+ {openManualSignIn && (
+ {
+ setOpenManualSignIn(false);
+ }}
+ />
+ )}
+
+
+ );
+}
+
+export default AppFlowyDevTool;
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/devtool/ManualSignInDialog.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/devtool/ManualSignInDialog.tsx
new file mode 100644
index 0000000000..364b334a07
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/devtool/ManualSignInDialog.tsx
@@ -0,0 +1,114 @@
+import React from 'react';
+import { CircularProgress, DialogActions, DialogProps, Tab, Tabs, TextareaAutosize } from '@mui/material';
+import Dialog from '@mui/material/Dialog';
+import DialogContent from '@mui/material/DialogContent';
+import Button from '@mui/material/Button';
+import { useAuth } from '$app/components/auth/auth.hooks';
+import TextField from '@mui/material/TextField';
+
+function ManualSignInDialog(props: DialogProps) {
+ const [uri, setUri] = React.useState('');
+ const [loading, setLoading] = React.useState(false);
+ const { signInWithOAuth, signInWithEmailPassword } = useAuth();
+ const [tab, setTab] = React.useState(0);
+ const [email, setEmail] = React.useState('');
+ const [password, setPassword] = React.useState('');
+ const [domain, setDomain] = React.useState('');
+ const handleSignIn = async () => {
+ setLoading(true);
+ try {
+ if (tab === 1) {
+ if (!email || !password) return;
+ await signInWithEmailPassword(email, password, domain);
+ } else {
+ await signInWithOAuth(uri);
+ }
+ } finally {
+ setLoading(false);
+ }
+
+ props?.onClose?.({}, 'backdropClick');
+ };
+
+ return (
+
+ );
+}
+
+export default ManualSignInDialog;
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/login/LoginButtonGroup.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/login/LoginButtonGroup.tsx
deleted file mode 100644
index 7334a94420..0000000000
--- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/login/LoginButtonGroup.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import Button from '@mui/material/Button';
-import GoogleIcon from '$app/assets/settings/google.png';
-import GithubIcon from '$app/assets/settings/github.png';
-import DiscordIcon from '$app/assets/settings/discord.png';
-import { useTranslation } from 'react-i18next';
-
-export const LoginButtonGroup = () => {
- const { t } = useTranslation();
-
- return (
-
-
-
-
-
- );
-};
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/login/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/login/index.ts
deleted file mode 100644
index 04605317ed..0000000000
--- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/login/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './LoginButtonGroup';
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/auth/LoginButtonGroup.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/auth/LoginButtonGroup.tsx
new file mode 100644
index 0000000000..481b80a532
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/auth/LoginButtonGroup.tsx
@@ -0,0 +1,51 @@
+import Button from '@mui/material/Button';
+import GoogleIcon from '$app/assets/settings/google.png';
+import GithubIcon from '$app/assets/settings/github.png';
+import DiscordIcon from '$app/assets/settings/discord.png';
+import { useTranslation } from 'react-i18next';
+import { useAuth } from '$app/components/auth/auth.hooks';
+import { ProviderTypePB } from '@/services/backend';
+
+export const LoginButtonGroup = () => {
+ const { t } = useTranslation();
+
+ const { signIn } = useAuth();
+
+ return (
+
+
+
+
+
+ );
+};
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/auth/ProtectedRoutes.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/auth/ProtectedRoutes.tsx
index 0d776bada5..523f0b5188 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/auth/ProtectedRoutes.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/auth/ProtectedRoutes.tsx
@@ -3,15 +3,24 @@ import { useAuth } from './auth.hooks';
import Layout from '$app/components/layout/Layout';
import { useCallback, useEffect, useState } from 'react';
import { Welcome } from '$app/components/auth/Welcome';
-import { ReactComponent as AppflowyLogo } from '$app/assets/logo.svg';
+import { isTauri } from '$app/utils/env';
+import { notify } from '$app/components/_shared/notify';
+import { currentUserActions, LoginState } from '$app_reducers/current-user/slice';
+import { CircularProgress, Portal } from '@mui/material';
+import { ReactComponent as Logo } from '$app/assets/logo.svg';
+import { useAppDispatch } from '$app/stores/store';
export const ProtectedRoutes = () => {
- const { currentUser, checkUser, subscribeToUser } = useAuth();
- const [isLoading, setIsLoading] = useState(true);
+ const { currentUser, checkUser, subscribeToUser, signInWithOAuth } = useAuth();
+ const dispatch = useAppDispatch();
+
+ const isLoading = currentUser?.loginState === LoginState.Loading;
+
+ const [checked, setChecked] = useState(false);
const checkUserStatus = useCallback(async () => {
await checkUser();
- setIsLoading(false);
+ setChecked(true);
}, [checkUser]);
useEffect(() => {
@@ -24,21 +33,73 @@ export const ProtectedRoutes = () => {
}
}, [currentUser.isAuthenticated, subscribeToUser]);
- if (isLoading) {
- // It's better to make a fading effect to disappear the loading page
- return ;
- } else {
- return ;
- }
+ const onDeepLink = useCallback(async () => {
+ if (!isTauri()) return;
+ const { event } = await import('@tauri-apps/api');
+
+ // On macOS You still have to install a .app bundle you got from tauri build --debug for this to work!
+ return await event.listen('open_deep_link', async (e) => {
+ const payload = e.payload as string;
+
+ const [, hash] = payload.split('//#');
+ const obj = parseHash(hash);
+
+ if (!obj.access_token) {
+ notify.error('Failed to sign in, the access token is missing');
+ dispatch(currentUserActions.setLoginState(LoginState.Error));
+ return;
+ }
+
+ try {
+ await signInWithOAuth(payload);
+ } catch (e) {
+ notify.error('Failed to sign in, please try again');
+ }
+ });
+ }, [dispatch, signInWithOAuth]);
+
+ useEffect(() => {
+ void onDeepLink();
+ }, [onDeepLink]);
+
+ return (
+
+ {checked ? (
+
+ ) : (
+
+
+
+ )}
+
+ {isLoading &&
}
+
+ );
};
const StartLoading = () => {
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ const preventDefault = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ e.preventDefault();
+ dispatch(currentUserActions.resetLoginState());
+ }
+ };
+
+ document.addEventListener('keydown', preventDefault, true);
+
+ return () => {
+ document.removeEventListener('keydown', preventDefault, true);
+ };
+ }, [dispatch]);
return (
-
-
+
);
};
@@ -53,3 +114,14 @@ const SplashScreen = ({ isAuthenticated }: { isAuthenticated: boolean }) => {
return
;
}
};
+
+function parseHash(hash: string) {
+ const hashParams = new URLSearchParams(hash);
+ const hashObject: Record
= {};
+
+ for (const [key, value] of hashParams) {
+ hashObject[key] = value;
+ }
+
+ return hashObject;
+}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/auth/Welcome.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/auth/Welcome.tsx
index 2dd0401412..c3c699e462 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/auth/Welcome.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/auth/Welcome.tsx
@@ -1,7 +1,7 @@
import { ReactComponent as AppflowyLogo } from '$app/assets/logo.svg';
import Button from '@mui/material/Button';
import { useTranslation } from 'react-i18next';
-import { LoginButtonGroup } from '$app/components/_shared/login';
+import { LoginButtonGroup } from '$app/components/auth/LoginButtonGroup';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '$app/components/auth/auth.hooks';
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/auth/auth.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/auth/auth.hooks.ts
index 56e44a4765..c49d65886f 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/auth/auth.hooks.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/auth/auth.hooks.ts
@@ -1,12 +1,13 @@
-import { currentUserActions } from '$app_reducers/current-user/slice';
-import { AuthenticatorPB, UserNotification, UserProfilePB } from '@/services/backend/events/flowy-user';
+import { currentUserActions, LoginState } from '$app_reducers/current-user/slice';
+import { AuthenticatorPB, ProviderTypePB, UserNotification, UserProfilePB } from '@/services/backend/events/flowy-user';
import { UserService } from '$app/application/user/user.service';
import { AuthService } from '$app/application/user/auth.service';
-import { useAppSelector, useAppDispatch } from '$app/stores/store';
+import { useAppDispatch, useAppSelector } from '$app/stores/store';
import { getCurrentWorkspaceSetting } from '$app/application/folder/workspace.service';
import { useCallback } from 'react';
import { subscribeNotifications } from '$app/application/notification';
import { nanoid } from 'nanoid';
+import { open } from '@tauri-apps/api/shell';
export const useAuth = () => {
const dispatch = useAppDispatch();
@@ -31,72 +32,49 @@ export const useAuth = () => {
};
}, [dispatch]);
- // Check if the user is authenticated
- const checkUser = useCallback(async () => {
- const userProfile = await UserService.getUserProfile();
+ const setUser = useCallback(
+ async (userProfile?: Partial) => {
+ if (!userProfile) return;
- if (!userProfile) return;
- const workspaceSetting = await getCurrentWorkspaceSetting();
-
- const isLocal = userProfile.authenticator === AuthenticatorPB.Local;
-
- dispatch(
- currentUserActions.checkUser({
- id: userProfile.id,
- token: userProfile.token,
- email: userProfile.email,
- displayName: userProfile.name,
- iconUrl: userProfile.icon_url,
- isAuthenticated: true,
- workspaceSetting: workspaceSetting,
- isLocal,
- })
- );
-
- return userProfile;
- }, [dispatch]);
-
- const register = useCallback(
- async (email: string, password: string, name: string): Promise => {
- const userProfile = await AuthService.signUp({ email, password, name });
-
- // Get the workspace setting after user registered. The workspace setting
- // contains the latest visiting page and the current workspace data.
const workspaceSetting = await getCurrentWorkspaceSetting();
+ const isLocal = userProfile.authenticator === AuthenticatorPB.Local;
+
dispatch(
currentUserActions.updateUser({
id: userProfile.id,
token: userProfile.token,
email: userProfile.email,
displayName: userProfile.name,
+ iconUrl: userProfile.icon_url,
isAuthenticated: true,
- workspaceSetting,
+ workspaceSetting: workspaceSetting,
+ isLocal,
})
);
-
- return userProfile;
},
[dispatch]
);
- const login = useCallback(
- async (email: string, password: string): Promise => {
- const user = await AuthService.signIn({ email, password });
- const { id, token, name } = user;
+ // Check if the user is authenticated
+ const checkUser = useCallback(async () => {
+ const userProfile = await UserService.getUserProfile();
- dispatch(
- currentUserActions.updateUser({
- id: id,
- token: token,
- email,
- displayName: name,
- isAuthenticated: true,
- })
- );
- return user;
+ await setUser(userProfile);
+
+ return userProfile;
+ }, [setUser]);
+
+ const register = useCallback(
+ async (email: string, password: string, name: string): Promise => {
+ const deviceId = currentUser?.deviceId ?? nanoid(8);
+ const userProfile = await AuthService.signUp({ deviceId, email, password, name });
+
+ await setUser(userProfile);
+
+ return userProfile;
},
- [dispatch]
+ [setUser, currentUser?.deviceId]
);
const logout = useCallback(async () => {
@@ -112,5 +90,97 @@ export const useAuth = () => {
await register(fakeEmail, fakePassword, fakeName);
}, [register]);
- return { currentUser, checkUser, register, login, logout, subscribeToUser, signInAsAnonymous };
+ const signIn = useCallback(
+ async (provider: ProviderTypePB) => {
+ dispatch(currentUserActions.setLoginState(LoginState.Loading));
+ try {
+ const url = await AuthService.getOAuthURL(provider);
+
+ await open(url);
+ } catch {
+ dispatch(currentUserActions.setLoginState(LoginState.Error));
+ }
+ },
+ [dispatch]
+ );
+
+ const signInWithOAuth = useCallback(
+ async (uri: string) => {
+ dispatch(currentUserActions.setLoginState(LoginState.Loading));
+ try {
+ const deviceId = currentUser?.deviceId ?? nanoid(8);
+
+ await AuthService.signInWithOAuth({ uri, deviceId });
+ const userProfile = await UserService.getUserProfile();
+
+ await setUser(userProfile);
+
+ return userProfile;
+ } catch (e) {
+ dispatch(currentUserActions.setLoginState(LoginState.Error));
+ return Promise.reject(e);
+ }
+ },
+ [dispatch, currentUser?.deviceId, setUser]
+ );
+
+ // Only for development purposes
+ const signInWithEmailPassword = useCallback(
+ async (email: string, password: string, domain?: string) => {
+ dispatch(currentUserActions.setLoginState(LoginState.Loading));
+
+ try {
+ const response = await fetch(
+ `https://${domain ? domain : 'test.appflowy.cloud'}/gotrue/token?grant_type=password`,
+ {
+ method: 'POST',
+ mode: 'cors',
+ cache: 'no-cache',
+ credentials: 'same-origin',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ redirect: 'follow',
+ referrerPolicy: 'no-referrer',
+ body: JSON.stringify({
+ email,
+ password,
+ }),
+ }
+ );
+
+ const data = await response.json();
+
+ let uri = `appflowy-flutter://#`;
+ const params: string[] = [];
+
+ Object.keys(data).forEach((key) => {
+ if (typeof data[key] === 'object') {
+ return;
+ }
+
+ params.push(`${key}=${data[key]}`);
+ });
+ uri += params.join('&');
+
+ return signInWithOAuth(uri);
+ } catch (e) {
+ dispatch(currentUserActions.setLoginState(LoginState.Error));
+ return Promise.reject(e);
+ }
+ },
+ [dispatch, signInWithOAuth]
+ );
+
+ return {
+ currentUser,
+ checkUser,
+ register,
+ logout,
+ subscribeToUser,
+ signInAsAnonymous,
+ signIn,
+ signInWithOAuth,
+ signInWithEmailPassword,
+ };
};
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/Workspace.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/Workspace.hooks.ts
index a754dcdf3b..c425835372 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/Workspace.hooks.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/Workspace.hooks.ts
@@ -1,4 +1,4 @@
-import { useCallback, useEffect } from 'react';
+import { useCallback, useEffect, useMemo } from 'react';
import { useAppDispatch, useAppSelector } from '$app/stores/store';
import { workspaceActions, WorkspaceItem } from '$app_reducers/workspace/slice';
import { Page, pagesActions, parserViewPBToPage } from '$app_reducers/pages/slice';
@@ -10,29 +10,29 @@ import { useNavigate } from 'react-router-dom';
export function useLoadWorkspaces() {
const dispatch = useAppDispatch();
- const { workspaces, currentWorkspace } = useAppSelector((state) => state.workspace);
+ const { workspaces, currentWorkspaceId } = useAppSelector((state) => state.workspace);
+
+ const currentWorkspace = useMemo(() => {
+ return workspaces.find((workspace) => workspace.id === currentWorkspaceId);
+ }, [workspaces, currentWorkspaceId]);
const initializeWorkspaces = useCallback(async () => {
const workspaces = await workspaceService.getWorkspaces();
- const currentWorkspace = await workspaceService.getCurrentWorkspace();
+
+ const currentWorkspaceId = await workspaceService.getCurrentWorkspace();
dispatch(
workspaceActions.initWorkspaces({
workspaces,
- currentWorkspace,
+ currentWorkspaceId,
})
);
}, [dispatch]);
- useEffect(() => {
- void (async () => {
- await initializeWorkspaces();
- })();
- }, [initializeWorkspaces]);
-
return {
workspaces,
currentWorkspace,
+ initializeWorkspaces,
};
}
@@ -82,8 +82,10 @@ export function useLoadWorkspace(workspace: WorkspaceItem) {
{
[FolderNotification.DidUpdateWorkspace]: async (changeset) => {
dispatch(
- workspaceActions.updateCurrentWorkspace({
+ workspaceActions.updateWorkspace({
+ id: String(changeset.id),
name: changeset.name,
+ icon: changeset.icon_url,
})
);
},
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/Workspace.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/Workspace.tsx
index b2f0fbb0ca..24fc7be91e 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/Workspace.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/Workspace.tsx
@@ -43,8 +43,20 @@ function Workspace({ workspace, opened }: { workspace: WorkspaceItem; opened: bo
>
-
- {workspace.name}
+ {!workspace.name ? (
+ t('sideBar.personal')
+ ) : (
+ <>
+
+ {workspace.name}
+ >
+ )}
{showAdd && (
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/WorkspaceManager.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/WorkspaceManager.tsx
index c6404d435c..4b4dbea10e 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/WorkspaceManager.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/WorkspaceManager.tsx
@@ -1,11 +1,21 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import NewPageButton from '$app/components/layout/workspace_manager/NewPageButton';
import { useLoadWorkspaces } from '$app/components/layout/workspace_manager/Workspace.hooks';
import Workspace from './Workspace';
import TrashButton from '$app/components/layout/workspace_manager/TrashButton';
+import { useAppSelector } from '@/appflowy_app/stores/store';
+import { LoginState } from '$app_reducers/current-user/slice';
function WorkspaceManager() {
- const { workspaces, currentWorkspace } = useLoadWorkspaces();
+ const { workspaces, currentWorkspace, initializeWorkspaces } = useLoadWorkspaces();
+
+ const loginState = useAppSelector((state) => state.currentUser.loginState);
+
+ useEffect(() => {
+ if (loginState === LoginState.Success || loginState === undefined) {
+ void initializeWorkspaces();
+ }
+ }, [initializeWorkspaces, loginState]);
return (
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/Login.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/settings/Login.tsx
index 6daceadb61..d5ecc4bc0c 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/Login.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/settings/Login.tsx
@@ -1,7 +1,7 @@
import Typography from '@mui/material/Typography';
import { useTranslation } from 'react-i18next';
import Button from '@mui/material/Button';
-import { LoginButtonGroup } from '$app/components/_shared/login';
+import { LoginButtonGroup } from '$app/components/auth/LoginButtonGroup';
export const Login = ({ onBack }: { onBack?: () => void }) => {
const { t } = useTranslation();
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/SettingsDialog.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/settings/SettingsDialog.tsx
index 72f2fddb49..9d2f02de1a 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/SettingsDialog.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/settings/SettingsDialog.tsx
@@ -4,7 +4,7 @@
import Dialog, { DialogProps } from '@mui/material/Dialog';
import { Settings } from '$app/components/settings/Settings';
-import { useCallback, useState } from 'react';
+import { useCallback, useEffect, useRef, useState } from 'react';
import DialogTitle from '@mui/material/DialogTitle';
import { IconButton } from '@mui/material';
import { ReactComponent as CloseIcon } from '$app/assets/close.svg';
@@ -14,10 +14,15 @@ import { SettingsRoutes } from '$app/components/settings/workplace/const';
import DialogContent from '@mui/material/DialogContent';
import { Login } from '$app/components/settings/Login';
import SwipeableViews from 'react-swipeable-views';
+import { useAppDispatch, useAppSelector } from '$app/stores/store';
+import { currentUserActions, LoginState } from '$app_reducers/current-user/slice';
+import { useNavigate } from 'react-router-dom';
export const SettingsDialog = (props: DialogProps) => {
+ const dispatch = useAppDispatch();
const [routes, setRoutes] = useState
([]);
-
+ const loginState = useAppSelector((state) => state.currentUser.loginState);
+ const lastLoginStateRef = useRef(loginState);
const { t } = useTranslation();
const handleForward = useCallback((route: SettingsRoutes) => {
setRoutes((prev) => {
@@ -29,14 +34,28 @@ export const SettingsDialog = (props: DialogProps) => {
setRoutes((prevState) => {
return prevState.slice(0, -1);
});
- }, []);
+ dispatch(currentUserActions.resetLoginState());
+ }, [dispatch]);
- const handleClose = () => {
+ const handleClose = useCallback(() => {
+ dispatch(currentUserActions.resetLoginState());
props?.onClose?.({}, 'backdropClick');
- };
+ }, [dispatch, props]);
const currentRoute = routes[routes.length - 1];
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ if (lastLoginStateRef.current === LoginState.Loading && loginState === LoginState.Success) {
+ navigate('/');
+ handleClose();
+ return;
+ }
+
+ lastLoginStateRef.current = loginState;
+ }, [loginState, handleClose, navigate]);
+
return (