mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Fix/tauri login state (#1919)
* fix: redux serializableCheck * chore: read login state * fix: show loading page while checking user state * chore: cell data parser
This commit is contained in:
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import TestApiButton from './TestApiButton';
|
|
||||||
import {
|
import {
|
||||||
TestCreateGrid,
|
TestCreateGrid,
|
||||||
TestCreateNewField,
|
TestCreateNewField,
|
||||||
@ -18,7 +17,7 @@ export const TestAPI = () => {
|
|||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ul className='m-6, space-y-2'>
|
<ul className='m-6, space-y-2'>
|
||||||
<TestApiButton></TestApiButton>
|
{/*<TestApiButton></TestApiButton>*/}
|
||||||
<TestCreateGrid></TestCreateGrid>
|
<TestCreateGrid></TestCreateGrid>
|
||||||
<TestCreateRow></TestCreateRow>
|
<TestCreateRow></TestCreateRow>
|
||||||
<TestDeleteRow></TestDeleteRow>
|
<TestDeleteRow></TestDeleteRow>
|
||||||
|
@ -126,8 +126,10 @@ export const TestCreateSelectOptionInCell = () => {
|
|||||||
);
|
);
|
||||||
await cellController.subscribeChanged({
|
await cellController.subscribeChanged({
|
||||||
onCellChanged: (value) => {
|
onCellChanged: (value) => {
|
||||||
|
if (value.some) {
|
||||||
const option: SelectOptionCellDataPB = value.unwrap();
|
const option: SelectOptionCellDataPB = value.unwrap();
|
||||||
console.log(option);
|
console.log(option);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const backendSvc = new SelectOptionCellBackendService(cellController.cellIdentifier);
|
const backendSvc = new SelectOptionCellBackendService(cellController.cellIdentifier);
|
||||||
|
@ -1,16 +1,54 @@
|
|||||||
import { Navigate, Outlet, useLocation } from 'react-router-dom';
|
import { Outlet } from 'react-router-dom';
|
||||||
import { useAuth } from './auth.hooks';
|
import { useAuth } from './auth.hooks';
|
||||||
import { Screen } from '../layout/Screen';
|
import { Screen } from '../layout/Screen';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { GetStarted } from './GetStarted/GetStarted';
|
||||||
|
import { AppflowyLogo } from '../_shared/svg/AppflowyLogo';
|
||||||
|
|
||||||
export const ProtectedRoutes = () => {
|
export const ProtectedRoutes = () => {
|
||||||
const location = useLocation();
|
const { currentUser, checkUser } = useAuth();
|
||||||
const { currentUser } = useAuth();
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
return currentUser.isAuthenticated ? (
|
useEffect(() => {
|
||||||
|
void checkUser().then(async (result) => {
|
||||||
|
if (result.err) {
|
||||||
|
throw new Error(result.val.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(() =>
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
}, 1200)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
// It's better to make a fading effect to disappear the loading page
|
||||||
|
return <StartLoading />;
|
||||||
|
} else {
|
||||||
|
return <SplashScreen isAuthenticated={currentUser.isAuthenticated} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const StartLoading = () => {
|
||||||
|
return (
|
||||||
|
<div className='flex h-screen w-full flex-col items-center justify-center'>
|
||||||
|
<div className='h-40 w-40 justify-center'>
|
||||||
|
<AppflowyLogo />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SplashScreen = ({ isAuthenticated }: { isAuthenticated: boolean }) => {
|
||||||
|
if (isAuthenticated) {
|
||||||
|
return (
|
||||||
<Screen>
|
<Screen>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Screen>
|
</Screen>
|
||||||
) : (
|
|
||||||
<Navigate to='/auth/getStarted' replace state={{ from: location }} />
|
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return <GetStarted></GetStarted>;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,20 +1,46 @@
|
|||||||
import { currentUserActions } from '../../stores/reducers/current-user/slice';
|
import { currentUserActions } from '../../stores/reducers/current-user/slice';
|
||||||
import { useAppDispatch, useAppSelector } from '../../stores/store';
|
import { useAppDispatch, useAppSelector } from '../../stores/store';
|
||||||
import { UserProfilePB } from '../../../services/backend/events/flowy-user';
|
import { UserProfilePB } from '../../../services/backend/events/flowy-user';
|
||||||
import { AuthBackendService } from '../../stores/effects/user/user_bd_svc';
|
import { AuthBackendService, UserBackendService } from '../../stores/effects/user/user_bd_svc';
|
||||||
import { FolderEventReadCurrentWorkspace } from '../../../services/backend/events/flowy-folder';
|
import { FolderEventReadCurrentWorkspace } from '../../../services/backend/events/flowy-folder';
|
||||||
import { WorkspaceSettingPB } from '../../../services/backend/models/flowy-folder/workspace';
|
import { WorkspaceSettingPB } from '../../../services/backend/models/flowy-folder/workspace';
|
||||||
|
import { Log } from '../../utils/log';
|
||||||
|
|
||||||
export const useAuth = () => {
|
export const useAuth = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const currentUser = useAppSelector((state) => state.currentUser);
|
const currentUser = useAppSelector((state) => state.currentUser);
|
||||||
const authBackendService = new AuthBackendService();
|
const authBackendService = new AuthBackendService();
|
||||||
|
|
||||||
|
async function checkUser() {
|
||||||
|
const result = await UserBackendService.checkUser();
|
||||||
|
if (result.ok) {
|
||||||
|
const userProfile = result.val;
|
||||||
|
const workspaceSetting = await _openWorkspace().then((r) => {
|
||||||
|
if (r.ok) {
|
||||||
|
return r.val;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dispatch(
|
||||||
|
currentUserActions.checkUser({
|
||||||
|
id: userProfile.id,
|
||||||
|
token: userProfile.token,
|
||||||
|
email: userProfile.email,
|
||||||
|
displayName: userProfile.name,
|
||||||
|
isAuthenticated: true,
|
||||||
|
workspaceSetting: workspaceSetting,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
async function register(email: string, password: string, name: string): Promise<UserProfilePB> {
|
async function register(email: string, password: string, name: string): Promise<UserProfilePB> {
|
||||||
const authResult = await authBackendService.signUp({ email, password, name });
|
const authResult = await authBackendService.signUp({ email, password, name });
|
||||||
|
|
||||||
if (authResult.ok) {
|
if (authResult.ok) {
|
||||||
const { id, token } = authResult.val;
|
const userProfile = authResult.val;
|
||||||
// Get the workspace setting after user registered. The workspace setting
|
// Get the workspace setting after user registered. The workspace setting
|
||||||
// contains the latest visiting view and the current workspace data.
|
// contains the latest visiting view and the current workspace data.
|
||||||
const openWorkspaceResult = await _openWorkspace();
|
const openWorkspaceResult = await _openWorkspace();
|
||||||
@ -22,10 +48,10 @@ export const useAuth = () => {
|
|||||||
const workspaceSetting: WorkspaceSettingPB = openWorkspaceResult.val;
|
const workspaceSetting: WorkspaceSettingPB = openWorkspaceResult.val;
|
||||||
dispatch(
|
dispatch(
|
||||||
currentUserActions.updateUser({
|
currentUserActions.updateUser({
|
||||||
id: id,
|
id: userProfile.id,
|
||||||
token: token,
|
token: userProfile.token,
|
||||||
email,
|
email: userProfile.email,
|
||||||
displayName: name,
|
displayName: userProfile.name,
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
workspaceSetting: workspaceSetting,
|
workspaceSetting: workspaceSetting,
|
||||||
})
|
})
|
||||||
@ -33,7 +59,7 @@ export const useAuth = () => {
|
|||||||
}
|
}
|
||||||
return authResult.val;
|
return authResult.val;
|
||||||
} else {
|
} else {
|
||||||
console.error(authResult.val.msg);
|
Log.error(authResult.val.msg);
|
||||||
throw new Error(authResult.val.msg);
|
throw new Error(authResult.val.msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,7 +79,7 @@ export const useAuth = () => {
|
|||||||
);
|
);
|
||||||
return result.val;
|
return result.val;
|
||||||
} else {
|
} else {
|
||||||
console.error(result.val.msg);
|
Log.error(result.val.msg);
|
||||||
throw new Error(result.val.msg);
|
throw new Error(result.val.msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,5 +93,5 @@ export const useAuth = () => {
|
|||||||
return FolderEventReadCurrentWorkspace();
|
return FolderEventReadCurrentWorkspace();
|
||||||
}
|
}
|
||||||
|
|
||||||
return { currentUser, register, login, logout };
|
return { currentUser, checkUser, register, login, logout };
|
||||||
};
|
};
|
||||||
|
@ -13,7 +13,7 @@ type Callbacks<T> = { onCellChanged: (value: Option<T>) => void; onFieldChanged?
|
|||||||
|
|
||||||
export class CellController<T, D> {
|
export class CellController<T, D> {
|
||||||
private fieldBackendService: FieldBackendService;
|
private fieldBackendService: FieldBackendService;
|
||||||
private cellDataNotifier: CellDataNotifier<Option<T>>;
|
private cellDataNotifier: CellDataNotifier<T>;
|
||||||
private cellObserver: CellObserver;
|
private cellObserver: CellObserver;
|
||||||
private readonly cacheKey: CellCacheKey;
|
private readonly cacheKey: CellCacheKey;
|
||||||
private readonly fieldNotifier: DatabaseFieldObserver;
|
private readonly fieldNotifier: DatabaseFieldObserver;
|
||||||
@ -59,7 +59,7 @@ export class CellController<T, D> {
|
|||||||
this.subscribeCallbacks = callbacks;
|
this.subscribeCallbacks = callbacks;
|
||||||
this.cellDataNotifier.observer.subscribe((cellData) => {
|
this.cellDataNotifier.observer.subscribe((cellData) => {
|
||||||
if (cellData !== null) {
|
if (cellData !== null) {
|
||||||
callbacks.onCellChanged(cellData);
|
callbacks.onCellChanged(Some(cellData));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -95,8 +95,11 @@ export class CellController<T, D> {
|
|||||||
private _loadCellData = () => {
|
private _loadCellData = () => {
|
||||||
return this.cellDataLoader.loadData().then((result) => {
|
return this.cellDataLoader.loadData().then((result) => {
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
this.cellCache.insert(this.cacheKey, result.val);
|
const cellData = result.val;
|
||||||
this.cellDataNotifier.cellData = Some(result.val);
|
if (cellData.some) {
|
||||||
|
this.cellCache.insert(this.cacheKey, cellData.val);
|
||||||
|
this.cellDataNotifier.cellData = cellData;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.cellCache.remove(this.cacheKey);
|
this.cellCache.remove(this.cacheKey);
|
||||||
this.cellDataNotifier.cellData = None;
|
this.cellDataNotifier.cellData = None;
|
||||||
@ -110,12 +113,12 @@ export class CellController<T, D> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class CellDataNotifier<T> extends ChangeNotifier<T | null> {
|
class CellDataNotifier<T> extends ChangeNotifier<T> {
|
||||||
_cellData: Option<T>;
|
_cellData: Option<T>;
|
||||||
|
|
||||||
constructor(cellData: T) {
|
constructor(cellData: Option<T>) {
|
||||||
super();
|
super();
|
||||||
this._cellData = Some(cellData);
|
this._cellData = cellData;
|
||||||
}
|
}
|
||||||
|
|
||||||
set cellData(data: Option<T>) {
|
set cellData(data: Option<T>) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { nanoid } from '@reduxjs/toolkit';
|
import { nanoid } from '@reduxjs/toolkit';
|
||||||
import {
|
import {
|
||||||
|
UserEventCheckUser,
|
||||||
UserEventGetUserProfile,
|
UserEventGetUserProfile,
|
||||||
UserEventSignIn,
|
UserEventSignIn,
|
||||||
UserEventSignOut,
|
UserEventSignOut,
|
||||||
@ -29,6 +30,10 @@ export class UserBackendService {
|
|||||||
return UserEventGetUserProfile();
|
return UserEventGetUserProfile();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static checkUser = () => {
|
||||||
|
return UserEventCheckUser();
|
||||||
|
};
|
||||||
|
|
||||||
updateUserProfile = (params: { name?: string; password?: string; email?: string; openAIKey?: string }) => {
|
updateUserProfile = (params: { name?: string; password?: string; email?: string; openAIKey?: string }) => {
|
||||||
const payload = UpdateUserProfilePayloadPB.fromObject({ id: this.userId });
|
const payload = UpdateUserProfilePayloadPB.fromObject({ id: this.userId });
|
||||||
|
|
||||||
|
@ -12,10 +12,6 @@ export interface ICurrentUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const initialState: ICurrentUser | null = {
|
const initialState: ICurrentUser | null = {
|
||||||
id: nanoid(8),
|
|
||||||
displayName: 'Me 😃',
|
|
||||||
email: `${nanoid(4)}@gmail.com`,
|
|
||||||
token: nanoid(8),
|
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -23,6 +19,9 @@ export const currentUserSlice = createSlice({
|
|||||||
name: 'currentUser',
|
name: 'currentUser',
|
||||||
initialState: initialState,
|
initialState: initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
|
checkUser: (state, action: PayloadAction<ICurrentUser>) => {
|
||||||
|
return action.payload;
|
||||||
|
},
|
||||||
updateUser: (state, action: PayloadAction<ICurrentUser>) => {
|
updateUser: (state, action: PayloadAction<ICurrentUser>) => {
|
||||||
return action.payload;
|
return action.payload;
|
||||||
},
|
},
|
||||||
|
@ -33,7 +33,7 @@ const store = configureStore({
|
|||||||
[workspaceSlice.name]: workspaceSlice.reducer,
|
[workspaceSlice.name]: workspaceSlice.reducer,
|
||||||
[errorSlice.name]: errorSlice.reducer,
|
[errorSlice.name]: errorSlice.reducer,
|
||||||
},
|
},
|
||||||
middleware: (gDM) => gDM().prepend(listenerMiddlewareInstance.middleware),
|
middleware: (gDM) => gDM({ serializableCheck: false }).prepend(listenerMiddlewareInstance.middleware),
|
||||||
});
|
});
|
||||||
|
|
||||||
export { store };
|
export { store };
|
||||||
|
Reference in New Issue
Block a user