diff --git a/client/src/api.ts b/client/src/api.ts index 385ab37f..33f6b55f 100644 --- a/client/src/api.ts +++ b/client/src/api.ts @@ -40,6 +40,7 @@ import { Organization, MentorTrainStatusById, RegenVttResponse, + LoginType, } from "types"; import { SearchParams } from "hooks/graphql/use-with-data-connection"; import { @@ -1957,13 +1958,14 @@ export async function login(accessToken: string): Promise { export async function loginGoogle( accessToken: string, - signupCode?: string + signupCode?: string, + loginType?: LoginType ): Promise { return execGql( { query: ` - mutation LoginGoogle($accessToken: String!, $signupCode: String, $lockMentorToConfig: Boolean) { - loginGoogle(accessToken: $accessToken, mentorConfig: $signupCode, lockMentorToConfig: $lockMentorToConfig) { + mutation LoginGoogle($accessToken: String!, $signupCode: String, $lockMentorToConfig: Boolean, $loginType: String) { + loginGoogle(accessToken: $accessToken, mentorConfig: $signupCode, lockMentorToConfig: $lockMentorToConfig, loginType: $loginType) { user { _id name @@ -1984,6 +1986,7 @@ export async function loginGoogle( accessToken: accessToken, signupCode: signupCode, lockMentorToConfig: true, + loginType: loginType, }, }, // login responds with set-cookie, w/o withCredentials it doesnt get stored diff --git a/client/src/components/login.tsx b/client/src/components/login.tsx index d007dd29..f87f84e4 100644 --- a/client/src/components/login.tsx +++ b/client/src/components/login.tsx @@ -20,6 +20,9 @@ import { ConfigStatus } from "store/slices/config"; import { useWithLogin } from "store/slices/login/useWithLogin"; import { OverridableTokenClientConfig } from "@react-oauth/google"; import { MentorConfig } from "types-gql"; +import { LoginType } from "types"; +import { LoginRejectedReason } from "store/slices/login"; +import { navigate } from "gatsby"; const useStyles = makeStyles({ name: { LoginPage } })((theme: Theme) => ({ toolbar: { @@ -46,8 +49,9 @@ function LoginPage(props: { overrideConfig?: OverridableTokenClientConfig | undefined ) => void; mentorConfig?: MentorConfig; + loginType: LoginType; }): JSX.Element { - const { mentorConfig } = props; + const { mentorConfig, loginType } = props; const { classes } = useStyles(); const { state: configState, loadConfig } = useWithConfig(); const { state: loginState, login } = useWithLogin(); @@ -99,8 +103,22 @@ function LoginPage(props: {
{/* create space below app bar */} {mentorConfig?.loginHeaderText || - "Please sign in to access the Mentor Studio portal"} + `Please sign ${ + loginType === LoginType.SIGN_IN ? "in" : "up" + } to access the Mentor Studio portal`} + {loginState.rejectedReason && ( + + {loginState.rejectedReason === + LoginRejectedReason.NO_ACCOUNT_FOUND ? ( +
+ No account found, please sign up. +
+ ) : ( + "Error signing in" + )} +
+ )} {process.env.ACCESS_TOKEN ? ( )} +
); } diff --git a/client/src/pages/index.tsx b/client/src/pages/index.tsx index c5593bcc..7fe9f5fd 100644 --- a/client/src/pages/index.tsx +++ b/client/src/pages/index.tsx @@ -12,6 +12,7 @@ import { useWithLogin } from "store/slices/login/useWithLogin"; import { LoginStatus } from "store/slices/login"; import { GoogleOAuthProvider, useGoogleLogin } from "@react-oauth/google"; +import { LoginType } from "types"; /** * Separate functional component in order for useGoogleLogin to be nested under GoogleOAuthProvider (This provider did not want to work in gatsby-browser, bug reported by others) @@ -20,13 +21,13 @@ function PrimaryDisplayHolder(): JSX.Element { const { state: loginState, loginWithGoogle } = useWithLogin(); const login = useGoogleLogin({ onSuccess: (tokenResponse) => { - loginWithGoogle(tokenResponse.access_token); + loginWithGoogle(tokenResponse.access_token, undefined, LoginType.SIGN_IN); }, }); if (loginState.loginStatus === LoginStatus.AUTHENTICATED) { return ; } else { - return ; + return ; } } diff --git a/client/src/pages/signup.tsx b/client/src/pages/signup.tsx index 1928e073..bc1fd06b 100644 --- a/client/src/pages/signup.tsx +++ b/client/src/pages/signup.tsx @@ -15,6 +15,7 @@ import { GoogleOAuthProvider, useGoogleLogin } from "@react-oauth/google"; import withLocation from "wrap-with-location"; import { fetchMentorConfig } from "api"; import { MentorConfig } from "types-gql"; +import { LoginType } from "types"; /** * Separate functional component in order for useGoogleLogin to be nested under GoogleOAuthProvider (This provider did not want to work in gatsby-browser, bug reported by others) @@ -38,14 +39,24 @@ function PrimaryDisplayHolder(): JSX.Element { }, []); const login = useGoogleLogin({ onSuccess: (tokenResponse) => { - loginWithGoogle(tokenResponse.access_token, signupCode); + loginWithGoogle( + tokenResponse.access_token, + signupCode, + LoginType.SIGN_UP + ); }, }); if (loginState.loginStatus === LoginStatus.AUTHENTICATED) { return ; } else { // Check for url param - return ; + return ( + + ); } } diff --git a/client/src/store/slices/login/index.ts b/client/src/store/slices/login/index.ts index 6ad1a490..f30e3218 100644 --- a/client/src/store/slices/login/index.ts +++ b/client/src/store/slices/login/index.ts @@ -14,7 +14,7 @@ import { sessionStorageClear, sessionStorageStore, } from "store/local-storage"; -import { User } from "types"; +import { LoginType, User } from "types"; import { extractErrorMessageFromError } from "helpers"; /** Store */ @@ -27,11 +27,19 @@ export enum LoginStatus { FAILED = 4, } +export enum LoginRejectedReason { + NONE = "NONE", + DISABLED = "DISABLED", + FAILED = "FAILED", + NO_ACCOUNT_FOUND = "NO_ACCOUNT_FOUND", +} + export interface LoginState { accessToken?: string; loginStatus: LoginStatus; user?: User; isDisabled?: boolean; + rejectedReason?: LoginRejectedReason; } const initialState: LoginState = { @@ -80,13 +88,15 @@ export const googleLogin = createAsyncThunk( args: { accessToken: string; signupCode?: string; + loginType?: LoginType; }, thunkAPI ) => { try { const googleLogin = await api.loginGoogle( args.accessToken, - args.signupCode + args.signupCode, + args.loginType ); localStorageStore(ACCESS_TOKEN_KEY, googleLogin.accessToken); sessionStorageClear(ACTIVE_MENTOR_KEY); @@ -182,8 +192,13 @@ export const loginSlice = createSlice({ ); } }) - .addCase(googleLogin.rejected, (state) => { + .addCase(googleLogin.rejected, (state, action) => { delete state.user; + if (action.error.message?.includes("No user found")) { + state.rejectedReason = LoginRejectedReason.NO_ACCOUNT_FOUND; + } else { + state.rejectedReason = LoginRejectedReason.FAILED; + } state.loginStatus = LoginStatus.FAILED; }) // Add cases for userSawSplashScreen action here, all you need for this one is the fulfilled case diff --git a/client/src/store/slices/login/useWithLogin.ts b/client/src/store/slices/login/useWithLogin.ts index 4c079c3e..2be408b9 100644 --- a/client/src/store/slices/login/useWithLogin.ts +++ b/client/src/store/slices/login/useWithLogin.ts @@ -8,13 +8,18 @@ import { useEffect } from "react"; import { useAppSelector, useAppDispatch } from "store/hooks"; import { ACCESS_TOKEN_KEY, localStorageGet } from "store/local-storage"; import * as loginActions from "."; +import { LoginType } from "types"; interface UseWithLogin { state: loginActions.LoginState; login: (accessToken: string) => void; userSawSplashScreen: (accessToken: string) => void; userSawTooltips: (accessToken: string) => void; - loginWithGoogle: (googleAccessToken: string, signupCode?: string) => void; + loginWithGoogle: ( + googleAccessToken: string, + signupCode?: string, + loginType?: LoginType + ) => void; logout: () => void; } @@ -45,14 +50,22 @@ export function useWithLogin(): UseWithLogin { } } - function loginWithGoogle(googleAccessToken: string, signupCode?: string) { + function loginWithGoogle( + googleAccessToken: string, + signupCode?: string, + loginType?: LoginType + ) { if ( state.loginStatus === loginActions.LoginStatus.NONE || state.loginStatus === loginActions.LoginStatus.NOT_LOGGED_IN || state.loginStatus === loginActions.LoginStatus.FAILED ) { dispatch( - loginActions.googleLogin({ accessToken: googleAccessToken, signupCode }) + loginActions.googleLogin({ + accessToken: googleAccessToken, + signupCode, + loginType, + }) ); } } diff --git a/client/src/store/slices/questions/index.ts b/client/src/store/slices/questions/index.ts index 0315f6d7..88cd4ea1 100644 --- a/client/src/store/slices/questions/index.ts +++ b/client/src/store/slices/questions/index.ts @@ -44,13 +44,14 @@ export const loadQuestionsById = createAsyncThunk( ): Promise> => { const state = thunkAPI.getState() as RootState; const ids = args.reload - ? args.ids + ? args.ids.filter((id) => id) : args.ids.filter((id) => { const q = getValueIfKeyExists(id, state.questions.questions); return ( - !q || - q.status === LoadingStatus.FAILED || - q.status === LoadingStatus.NONE + id && + (!q || + q.status === LoadingStatus.FAILED || + q.status === LoadingStatus.NONE) ); }); if (ids.length === 0) { diff --git a/client/src/types.ts b/client/src/types.ts index 567f3ed4..1910ff65 100644 --- a/client/src/types.ts +++ b/client/src/types.ts @@ -487,6 +487,11 @@ export enum EditType { OLD_ANSWER = "OLD_ANSWER", } +export enum LoginType { + SIGN_IN = "SIGN_IN", + SIGN_UP = "SIGN_UP", +} + export enum LoginStatus { NONE = 0, IN_PROGRESS = 1,