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:
Nathan.fooo
2023-03-04 11:03:16 +08:00
committed by GitHub
parent 3f0d3d802a
commit 205b0fc4a5
9 changed files with 106 additions and 34 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -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>

View File

@ -126,8 +126,10 @@ export const TestCreateSelectOptionInCell = () => {
); );
await cellController.subscribeChanged({ await cellController.subscribeChanged({
onCellChanged: (value) => { onCellChanged: (value) => {
const option: SelectOptionCellDataPB = value.unwrap(); if (value.some) {
console.log(option); const option: SelectOptionCellDataPB = value.unwrap();
console.log(option);
}
}, },
}); });
const backendSvc = new SelectOptionCellBackendService(cellController.cellIdentifier); const backendSvc = new SelectOptionCellBackendService(cellController.cellIdentifier);

View File

@ -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(() => {
<Screen> void checkUser().then(async (result) => {
<Outlet /> if (result.err) {
</Screen> throw new Error(result.val.msg);
) : ( }
<Navigate to='/auth/getStarted' replace state={{ from: location }} />
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>
<Outlet />
</Screen>
);
} else {
return <GetStarted></GetStarted>;
}
};

View File

@ -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 };
}; };

View File

@ -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>) {

View File

@ -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 });

View File

@ -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;
}, },

View File

@ -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 };