Skip to content

Commit ab4216e

Browse files
authored
♻️ refactor: refactor the home redirect implement (#2626)
* ♻️ refactor: refactor to improve loading rediect * ♻️ refactor: refactor to improve loading rediect
1 parent ce85cc6 commit ab4216e

File tree

12 files changed

+73
-31
lines changed

12 files changed

+73
-31
lines changed

src/app/(loading)/Redirect.tsx

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,47 @@
33
import { useRouter } from 'next/navigation';
44
import { memo, useEffect } from 'react';
55

6-
import { messageService } from '@/services/message';
7-
import { sessionService } from '@/services/session';
86
import { useUserStore } from '@/store/user';
97
import { authSelectors } from '@/store/user/selectors';
108

11-
const checkHasConversation = async () => {
12-
const hasMessages = await messageService.hasMessages();
13-
const hasAgents = await sessionService.hasSessions();
14-
return hasMessages || hasAgents;
15-
};
16-
179
const Redirect = memo(() => {
1810
const router = useRouter();
19-
const isLogin = useUserStore(authSelectors.isLogin);
11+
const [isLogin, isLoaded, isUserStateInit, isUserHasConversation, isOnboard] = useUserStore(
12+
(s) => [
13+
authSelectors.isLogin(s),
14+
authSelectors.isLoaded(s),
15+
s.isUserStateInit,
16+
s.isUserHasConversation,
17+
s.isOnboard,
18+
],
19+
);
2020

2121
useEffect(() => {
22+
// if user auth state is not ready, wait for loading
23+
if (!isLoaded) return;
24+
25+
// this mean user is definitely not login
2226
if (!isLogin) {
2327
router.replace('/welcome');
2428
return;
2529
}
2630

27-
checkHasConversation().then((hasData) => {
28-
if (hasData) {
29-
router.replace('/chat');
30-
} else {
31-
router.replace('/welcome');
32-
}
33-
});
34-
}, []);
31+
// if user state not init, wait for loading
32+
if (!isUserStateInit) return;
33+
34+
// user need to onboard
35+
if (!isOnboard) {
36+
router.replace('/onboard');
37+
return;
38+
}
39+
40+
// finally check the conversation status
41+
if (isUserHasConversation) {
42+
router.replace('/chat');
43+
} else {
44+
router.replace('/welcome');
45+
}
46+
}, [isUserStateInit, isLoaded, isUserHasConversation, isOnboard, isLogin]);
3547

3648
return null;
3749
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use client';
2+
3+
import { PropsWithChildren, memo } from 'react';
4+
import { createStoreUpdater } from 'zustand-utils';
5+
6+
import { useUserStore } from '@/store/user';
7+
8+
const NoAuthProvider = memo<PropsWithChildren>(({ children }) => {
9+
const useStoreUpdater = createStoreUpdater(useUserStore);
10+
11+
useStoreUpdater('isLoaded', true);
12+
13+
return children;
14+
});
15+
16+
export default NoAuthProvider;

src/layout/AuthProvider/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import { authEnv } from '@/config/auth';
44

55
import Clerk from './Clerk';
66
import NextAuth from './NextAuth';
7+
import NoAuth from './NoAuth';
78

89
const AuthProvider = ({ children }: PropsWithChildren) => {
910
if (authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH) return <Clerk>{children}</Clerk>;
1011

1112
if (authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH) return <NextAuth>{children}</NextAuth>;
1213

13-
return children;
14+
return <NoAuth>{children}</NoAuth>;
1415
};
1516

1617
export default AuthProvider;

src/middleware.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,7 @@ const nextAuthMiddleware = auth((req) => {
4141
});
4242

4343
export default authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH
44-
? // can't lift to a function because if there is no clerk public key, it will throw error
45-
clerkMiddleware((auth, request) => {
46-
// if user is logged in and on the home page, redirect to chat
47-
if (auth().userId && request.nextUrl.pathname === '/') {
48-
request.nextUrl.pathname = '/chat';
49-
return NextResponse.redirect(request.nextUrl);
50-
}
51-
52-
return NextResponse.next();
53-
})
44+
? clerkMiddleware()
5445
: authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH
5546
? nextAuthMiddleware
5647
: defaultMiddleware;

src/services/user/client.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ describe('ClientService', () => {
4646
expect(userState).toEqual({
4747
avatar: mockUser.avatar,
4848
isOnboard: true,
49+
canEnablePWAGuide: false,
50+
hasConversation: false,
4951
canEnableTrace: false,
5052
preference: mockPreference,
5153
settings: mockUser.settings,

src/services/user/client.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { DeepPartial } from 'utility-types';
22

33
import { MessageModel } from '@/database/client/models/message';
4+
import { SessionModel } from '@/database/client/models/session';
45
import { UserModel } from '@/database/client/models/user';
56
import { GlobalSettings } from '@/types/settings';
67
import { UserInitializationState, UserPreference } from '@/types/user';
@@ -18,10 +19,13 @@ export class ClientService implements IUserService {
1819
async getUserState(): Promise<UserInitializationState> {
1920
const user = await UserModel.getUser();
2021
const messageCount = await MessageModel.count();
22+
const sessionCount = await SessionModel.count();
2123

2224
return {
2325
avatar: user.avatar,
26+
canEnablePWAGuide: messageCount >= 2,
2427
canEnableTrace: messageCount >= 4,
28+
hasConversation: messageCount > 0 || sessionCount > 0,
2529
isOnboard: true,
2630
preference: await this.preferenceStorage.getFromLocalStorage(),
2731
settings: user.settings as GlobalSettings,

src/services/user/type.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { UserInitializationState, UserPreference } from '@/types/user';
66
export interface IUserService {
77
getUserState: () => Promise<UserInitializationState>;
88
resetUserSettings: () => Promise<any>;
9-
updateAvatar: (avatar: string) => Promise<any>;
109
updatePreference: (preference: UserPreference) => Promise<any>;
1110
updateUserSettings: (patch: DeepPartial<GlobalSettings>) => Promise<any>;
1211
}

src/store/user/slices/auth/selectors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const isLogin = (s: UserStore) => {
4141
};
4242

4343
export const authSelectors = {
44+
isLoaded: (s: UserStore) => s.isLoaded,
4445
isLogin,
4546
isLoginWithAuth: (s: UserStore) => s.isSignedIn,
4647
isLoginWithClerk: (s: UserStore): boolean => (s.isSignedIn && enableClerk) || false,

src/store/user/slices/common/action.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { withSWR } from '~test-utils';
55

66
import { DEFAULT_PREFERENCE } from '@/const/user';
77
import { userService } from '@/services/user';
8+
import { ClientService } from '@/services/user/client';
89
import { useUserStore } from '@/store/user';
910
import { preferenceSelectors } from '@/store/user/selectors';
1011
import { GlobalServerConfig } from '@/types/serverConfig';
@@ -36,7 +37,7 @@ describe('createCommonSlice', () => {
3637
const avatar = 'new-avatar';
3738

3839
const spyOn = vi.spyOn(result.current, 'refreshUserState');
39-
const updateAvatarSpy = vi.spyOn(userService, 'updateAvatar');
40+
const updateAvatarSpy = vi.spyOn(ClientService.prototype, 'updateAvatar');
4041

4142
await act(async () => {
4243
await result.current.updateAvatar(avatar);

src/store/user/slices/common/action.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { StateCreator } from 'zustand/vanilla';
44

55
import { DEFAULT_PREFERENCE } from '@/const/user';
66
import { userService } from '@/services/user';
7+
import { ClientService } from '@/services/user/client';
78
import type { UserStore } from '@/store/user';
89
import type { GlobalServerConfig } from '@/types/serverConfig';
910
import type { GlobalSettings } from '@/types/settings';
@@ -45,7 +46,9 @@ export const createCommonSlice: StateCreator<
4546
await mutate(GET_USER_STATE_KEY);
4647
},
4748
updateAvatar: async (avatar) => {
48-
await userService.updateAvatar(avatar);
49+
const clientService = new ClientService();
50+
51+
await clientService.updateAvatar(avatar);
4952
await get().refreshUserState();
5053
},
5154

@@ -89,8 +92,12 @@ export const createCommonSlice: StateCreator<
8992
{
9093
defaultSettings,
9194
enabledNextAuth: serverConfig.enabledOAuthSSO,
95+
isOnboard: data.isOnboard,
96+
isShowPWAGuide: data.canEnablePWAGuide,
9297
isUserCanEnableTrace: data.canEnableTrace,
98+
isUserHasConversation: data.hasConversation,
9399
isUserStateInit: true,
100+
94101
preference,
95102
serverLanguageModel: serverConfig.languageModel,
96103
settings: data.settings || {},
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
export interface CommonState {
2+
isOnboard: boolean;
3+
isShowPWAGuide: boolean;
24
isUserCanEnableTrace: boolean;
5+
isUserHasConversation: boolean;
36
isUserStateInit: boolean;
47
}
58

69
export const initialCommonState: CommonState = {
10+
isOnboard: false,
11+
isShowPWAGuide: false,
712
isUserCanEnableTrace: false,
13+
isUserHasConversation: false,
814
isUserStateInit: false,
915
};

src/types/user/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ export interface UserPreference {
3434

3535
export interface UserInitializationState {
3636
avatar?: string;
37+
canEnablePWAGuide?: boolean;
3738
canEnableTrace?: boolean;
39+
hasConversation?: boolean;
3840
isOnboard?: boolean;
3941
preference: UserPreference;
4042
settings: DeepPartial<GlobalSettings>;

0 commit comments

Comments
 (0)