Skip to content

Commit

Permalink
♻️ refactor: refactor the home redirect implement (#2626)
Browse files Browse the repository at this point in the history
* ♻️ refactor: refactor to improve loading rediect

* ♻️ refactor: refactor to improve loading rediect
  • Loading branch information
arvinxx authored May 23, 2024
1 parent ce85cc6 commit ab4216e
Show file tree
Hide file tree
Showing 12 changed files with 73 additions and 31 deletions.
46 changes: 29 additions & 17 deletions src/app/(loading)/Redirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,47 @@
import { useRouter } from 'next/navigation';
import { memo, useEffect } from 'react';

import { messageService } from '@/services/message';
import { sessionService } from '@/services/session';
import { useUserStore } from '@/store/user';
import { authSelectors } from '@/store/user/selectors';

const checkHasConversation = async () => {
const hasMessages = await messageService.hasMessages();
const hasAgents = await sessionService.hasSessions();
return hasMessages || hasAgents;
};

const Redirect = memo(() => {
const router = useRouter();
const isLogin = useUserStore(authSelectors.isLogin);
const [isLogin, isLoaded, isUserStateInit, isUserHasConversation, isOnboard] = useUserStore(
(s) => [
authSelectors.isLogin(s),
authSelectors.isLoaded(s),
s.isUserStateInit,
s.isUserHasConversation,
s.isOnboard,
],
);

useEffect(() => {
// if user auth state is not ready, wait for loading
if (!isLoaded) return;

// this mean user is definitely not login
if (!isLogin) {
router.replace('/welcome');
return;
}

checkHasConversation().then((hasData) => {
if (hasData) {
router.replace('/chat');
} else {
router.replace('/welcome');
}
});
}, []);
// if user state not init, wait for loading
if (!isUserStateInit) return;

// user need to onboard
if (!isOnboard) {
router.replace('/onboard');
return;
}

// finally check the conversation status
if (isUserHasConversation) {
router.replace('/chat');
} else {
router.replace('/welcome');
}
}, [isUserStateInit, isLoaded, isUserHasConversation, isOnboard, isLogin]);

return null;
});
Expand Down
16 changes: 16 additions & 0 deletions src/layout/AuthProvider/NoAuth/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client';

import { PropsWithChildren, memo } from 'react';
import { createStoreUpdater } from 'zustand-utils';

import { useUserStore } from '@/store/user';

const NoAuthProvider = memo<PropsWithChildren>(({ children }) => {
const useStoreUpdater = createStoreUpdater(useUserStore);

useStoreUpdater('isLoaded', true);

return children;
});

export default NoAuthProvider;
3 changes: 2 additions & 1 deletion src/layout/AuthProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { authEnv } from '@/config/auth';

import Clerk from './Clerk';
import NextAuth from './NextAuth';
import NoAuth from './NoAuth';

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

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

return children;
return <NoAuth>{children}</NoAuth>;
};

export default AuthProvider;
11 changes: 1 addition & 10 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,7 @@ const nextAuthMiddleware = auth((req) => {
});

export default authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH
? // can't lift to a function because if there is no clerk public key, it will throw error
clerkMiddleware((auth, request) => {
// if user is logged in and on the home page, redirect to chat
if (auth().userId && request.nextUrl.pathname === '/') {
request.nextUrl.pathname = '/chat';
return NextResponse.redirect(request.nextUrl);
}

return NextResponse.next();
})
? clerkMiddleware()
: authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH
? nextAuthMiddleware
: defaultMiddleware;
2 changes: 2 additions & 0 deletions src/services/user/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ describe('ClientService', () => {
expect(userState).toEqual({
avatar: mockUser.avatar,
isOnboard: true,
canEnablePWAGuide: false,
hasConversation: false,
canEnableTrace: false,
preference: mockPreference,
settings: mockUser.settings,
Expand Down
4 changes: 4 additions & 0 deletions src/services/user/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DeepPartial } from 'utility-types';

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

return {
avatar: user.avatar,
canEnablePWAGuide: messageCount >= 2,
canEnableTrace: messageCount >= 4,
hasConversation: messageCount > 0 || sessionCount > 0,
isOnboard: true,
preference: await this.preferenceStorage.getFromLocalStorage(),
settings: user.settings as GlobalSettings,
Expand Down
1 change: 0 additions & 1 deletion src/services/user/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { UserInitializationState, UserPreference } from '@/types/user';
export interface IUserService {
getUserState: () => Promise<UserInitializationState>;
resetUserSettings: () => Promise<any>;
updateAvatar: (avatar: string) => Promise<any>;
updatePreference: (preference: UserPreference) => Promise<any>;
updateUserSettings: (patch: DeepPartial<GlobalSettings>) => Promise<any>;
}
1 change: 1 addition & 0 deletions src/store/user/slices/auth/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const isLogin = (s: UserStore) => {
};

export const authSelectors = {
isLoaded: (s: UserStore) => s.isLoaded,
isLogin,
isLoginWithAuth: (s: UserStore) => s.isSignedIn,
isLoginWithClerk: (s: UserStore): boolean => (s.isSignedIn && enableClerk) || false,
Expand Down
3 changes: 2 additions & 1 deletion src/store/user/slices/common/action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { withSWR } from '~test-utils';

import { DEFAULT_PREFERENCE } from '@/const/user';
import { userService } from '@/services/user';
import { ClientService } from '@/services/user/client';
import { useUserStore } from '@/store/user';
import { preferenceSelectors } from '@/store/user/selectors';
import { GlobalServerConfig } from '@/types/serverConfig';
Expand Down Expand Up @@ -36,7 +37,7 @@ describe('createCommonSlice', () => {
const avatar = 'new-avatar';

const spyOn = vi.spyOn(result.current, 'refreshUserState');
const updateAvatarSpy = vi.spyOn(userService, 'updateAvatar');
const updateAvatarSpy = vi.spyOn(ClientService.prototype, 'updateAvatar');

await act(async () => {
await result.current.updateAvatar(avatar);
Expand Down
9 changes: 8 additions & 1 deletion src/store/user/slices/common/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { StateCreator } from 'zustand/vanilla';

import { DEFAULT_PREFERENCE } from '@/const/user';
import { userService } from '@/services/user';
import { ClientService } from '@/services/user/client';
import type { UserStore } from '@/store/user';
import type { GlobalServerConfig } from '@/types/serverConfig';
import type { GlobalSettings } from '@/types/settings';
Expand Down Expand Up @@ -45,7 +46,9 @@ export const createCommonSlice: StateCreator<
await mutate(GET_USER_STATE_KEY);
},
updateAvatar: async (avatar) => {
await userService.updateAvatar(avatar);
const clientService = new ClientService();

await clientService.updateAvatar(avatar);
await get().refreshUserState();
},

Expand Down Expand Up @@ -89,8 +92,12 @@ export const createCommonSlice: StateCreator<
{
defaultSettings,
enabledNextAuth: serverConfig.enabledOAuthSSO,
isOnboard: data.isOnboard,
isShowPWAGuide: data.canEnablePWAGuide,
isUserCanEnableTrace: data.canEnableTrace,
isUserHasConversation: data.hasConversation,
isUserStateInit: true,

preference,
serverLanguageModel: serverConfig.languageModel,
settings: data.settings || {},
Expand Down
6 changes: 6 additions & 0 deletions src/store/user/slices/common/initialState.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
export interface CommonState {
isOnboard: boolean;
isShowPWAGuide: boolean;
isUserCanEnableTrace: boolean;
isUserHasConversation: boolean;
isUserStateInit: boolean;
}

export const initialCommonState: CommonState = {
isOnboard: false,
isShowPWAGuide: false,
isUserCanEnableTrace: false,
isUserHasConversation: false,
isUserStateInit: false,
};
2 changes: 2 additions & 0 deletions src/types/user/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ export interface UserPreference {

export interface UserInitializationState {
avatar?: string;
canEnablePWAGuide?: boolean;
canEnableTrace?: boolean;
hasConversation?: boolean;
isOnboard?: boolean;
preference: UserPreference;
settings: DeepPartial<GlobalSettings>;
Expand Down

0 comments on commit ab4216e

Please sign in to comment.