Skip to content

Commit

Permalink
feat: updated authentication pending status (#178)
Browse files Browse the repository at this point in the history
  • Loading branch information
Fran McDade authored and Fran McDade committed Oct 31, 2024
1 parent c1669ae commit ae467ab
Show file tree
Hide file tree
Showing 18 changed files with 108 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import {
ButtonProps as MButtonProps,
IconButton as MIconButton,
IconButtonProps as MIconButtonProps,
Skeleton,
} from "@mui/material";
import Router from "next/router";
import React, { ElementType } from "react";
import { useProfile } from "../../../../../../../../../../hooks/authentication/profile/useProfile";
import { ROUTE } from "../../../../../../../../../../routes/constants";
import { isNavigationLinkSelected } from "../../../Navigation/common/utils";
import { AuthenticationMenu } from "./components/AuthenticationMenu/authenticationMenu";
import { StyledButton } from "./components/Button/button.styles";

Expand All @@ -22,8 +24,9 @@ export const Authentication = ({
Button,
closeMenu,
}: AuthenticationProps): JSX.Element | null => {
const { profile } = useProfile();
const { isLoading, profile } = useProfile();
if (!authenticationEnabled) return null;
if (isLoading) return <Skeleton height={32} variant="circular" width={32} />;
if (profile) return <AuthenticationMenu profile={profile} />;
return (
<Button
Expand All @@ -38,11 +41,21 @@ export const Authentication = ({
/**
* Renders authentication button.
* @param props - Button props.
* @param pathname - Pathname.
* @returns button.
*/
export function renderButton(props: MButtonProps): JSX.Element {
export function renderButton(
props: MButtonProps,
pathname: string
): JSX.Element {
return (
<StyledButton startIcon={<LoginRoundedIcon />} variant="nav" {...props}>
<StyledButton
startIcon={<LoginRoundedIcon />}
variant={
isNavigationLinkSelected(pathname, [ROUTE.LOGIN]) ? "activeNav" : "nav"
}
{...props}
>
Sign in
</StyledButton>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from "@emotion/styled";
import {
Avatar as MAvatar,
Avatar,
IconButton as MIconButton,
Menu as MMenu,
MenuItem,
Expand Down Expand Up @@ -33,7 +33,7 @@ export const UserNames = styled(Typography)`
max-width: 200px;
`;

export const Avatar = styled(MAvatar)`
export const StyledAvatar = styled(Avatar)`
height: 32px;
width: 32px;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { useAuth } from "../../../../../../../../../../../../providers/authentic
import { UserProfile } from "../../../../../../../../../../../../providers/authentication/authentication/types";
import { useMenu } from "../../../../../../../../../../../common/Menu/hooks/useMenu";
import {
Avatar,
AuthenticationMenu as Menu,
StyledAvatar,
UserIcon,
UserNames,
UserSummary,
Expand All @@ -24,7 +24,7 @@ export const AuthenticationMenu = ({
return (
<Fragment>
<UserIcon onClick={onOpen}>
<Avatar alt={profile.name} src={profile.image} />
<StyledAvatar alt={profile.name} src={profile.image} />
</UserIcon>
<Menu {...MENU_PROPS} anchorEl={anchorEl} onClose={onClose} open={open}>
<UserSummary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import styled from "@emotion/styled";
import { Button as MButton } from "@mui/material";
import { Button } from "@mui/material";

export const StyledButton = styled(MButton)`
export const StyledButton = styled(Button)`
&.MuiButton-activeNav,
&.MuiButton-nav {
padding: 6px 12px;
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Layout/components/Header/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export const Header = ({ ...headerProps }: HeaderProps): JSX.Element => {
Button={({ ...props }): JSX.Element =>
isIn.isMenuIn
? renderAuthenticationIconButton(props)
: renderAuthenticationButton(props)
: renderAuthenticationButton(props, pathname)
}
authenticationEnabled={authenticationEnabled}
closeMenu={onClose}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AlertProps as MAlertProps } from "@mui/material";
import React, { Fragment, ReactNode } from "react";
import { useSessionTimeout } from "../../../../../hooks/useSessionTimeout";
import { useSessionTimeout } from "../../../../../hooks/authentication/session/useSessionTimeout";
import { Banner } from "./sessionTimeout.styles";

export interface SessionTimeoutProps extends Omit<MAlertProps, "title"> {
Expand Down
1 change: 1 addition & 0 deletions src/hooks/authentication/profile/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ import {
} from "../../../providers/authentication/authentication/types";

export interface UseProfile {
isLoading: boolean;
profile: Profile<UserProfile>;
}
8 changes: 7 additions & 1 deletion src/hooks/authentication/profile/useProfile.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useAuth } from "../../../providers/authentication/auth/hook";
import { AUTH_STATUS } from "../../../providers/authentication/auth/types";
import { useAuthentication } from "../../../providers/authentication/authentication/hook";
import { UseProfile } from "./types";

Expand All @@ -6,10 +8,14 @@ import { UseProfile } from "./types";
* @returns authentication context.
*/
export const useProfile = (): UseProfile => {
const {
authState: { status },
} = useAuth();
const {
authenticationState: { profile },
} = useAuthentication();
return {
profile,
isLoading: status === AUTH_STATUS.PENDING,
profile: profile,
};
};
2 changes: 1 addition & 1 deletion src/hooks/authentication/session/useSessionActive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from "../../../providers/authentication/auth/types";
import { ROUTE } from "../../../routes/constants";
import { useRouteHistory } from "../../useRouteHistory";
import { INACTIVITY_PARAM } from "../../useSessionTimeout";
import { INACTIVITY_PARAM } from "./useSessionTimeout";

export const useSessionActive = (authState: AuthState): void => {
const { status } = authState;
Expand Down
23 changes: 4 additions & 19 deletions src/hooks/authentication/session/useSessionCallbackUrl.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,17 @@
import { useRouter } from "next/router";
import { useMemo } from "react";
import { useConfig } from "../../useConfig";
import { INACTIVITY_PARAM } from "../../useSessionTimeout";
import { useRouteRoot } from "../../useRouteRoot";
import { INACTIVITY_PARAM } from "./useSessionTimeout";

export interface UseSessionCallbackUrl {
callbackUrl: string | undefined;
}

export const useSessionCallbackUrl = (): UseSessionCallbackUrl => {
const {
config: { redirectRootToPath: path },
} = useConfig();
const { basePath } = useRouter();
const pathname = getPathname(basePath, path);
const callbackUrl = useMemo(() => getUrl(pathname), [pathname]);
const route = useRouteRoot();
const callbackUrl = useMemo(() => getUrl(route), [route]);
return { callbackUrl };
};

/**
* Returns the pathname.
* @param basePath - Base path.
* @param path - Path.
* @returns pathname.
*/
function getPathname(basePath: string, path: string): string {
return `${basePath}${path}`;
}

/**
* Returns the URL with the inactivity query parameter set to true.
* @param url - URL.
Expand Down
10 changes: 10 additions & 0 deletions src/hooks/authentication/session/useSessionIdleTimer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useIdleTimer } from "react-idle-timer";
import { IIdleTimerProps } from "react-idle-timer/dist/types/IIdleTimerProps";

/**
* Sets a session timeout that triggers when the user has been idle for the specified duration.
* @param idleTimerProps - The parameters for the session timeout.
*/
export const useSessionIdleTimer = (idleTimerProps: IIdleTimerProps): void => {
useIdleTimer(idleTimerProps);
};
42 changes: 36 additions & 6 deletions src/hooks/authentication/session/useSessionTimeout.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,40 @@
import { useIdleTimer } from "react-idle-timer";
import { IIdleTimerProps } from "react-idle-timer/dist/types/IIdleTimerProps";
import Router from "next/router";
import { useCallback, useEffect, useState } from "react";
import { useConfig } from "../../useConfig";
import { useLocation } from "../../useLocation";

export const INACTIVITY_PARAM = "inactivityTimeout";

interface UseSessionTimeout {
clearSessionTimeout: () => void;
isSessionTimeout: boolean;
}

/**
* Sets a session timeout that triggers when the user has been idle for the specified duration.
* @param idleTimerProps - The parameters for the session timeout.
* Session timeout hook.
* @returns flag indicating if the session has timed out.
*/
export const useSessionTimeout = (idleTimerProps: IIdleTimerProps): void => {
useIdleTimer(idleTimerProps);
export const useSessionTimeout = (): UseSessionTimeout => {
const {
config: { redirectRootToPath },
} = useConfig();
const [isSessionTimeout, setIsSessionTimeout] = useState<boolean>(false);
// Get the session timeout from URL parameters.
const { search } = useLocation() || {};
const sessionTimeout = search?.get(INACTIVITY_PARAM);

// Clears session timeout state.
const clearSessionTimeout = useCallback((): void => {
setIsSessionTimeout(false);
Router.replace(redirectRootToPath);
}, [redirectRootToPath]);

useEffect(() => {
setIsSessionTimeout(sessionTimeout === "true");
}, [sessionTimeout]);

return {
clearSessionTimeout,
isSessionTimeout,
};
};
14 changes: 8 additions & 6 deletions src/hooks/useRouteHistory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Router, { useRouter } from "next/router";
import { useCallback, useEffect, useRef } from "react";
import { useRouteRoot } from "./useRouteRoot";

const ROUTE_CHANGE_EVENT = "routeChangeComplete";
const MAX_HISTORY_LENGTH = 4;
Expand All @@ -14,8 +15,8 @@ export interface UseRouteHistory {
export function useRouteHistory(
maxHistory = MAX_HISTORY_LENGTH
): UseRouteHistory {
const { asPath, basePath } = useRouter();
const rootPath = basePath.trim() || "/";
const { asPath } = useRouter();
const rootPath = useRouteRoot();
const historyRef = useRef<string[]>([asPath]);

const onRouteChange = useCallback(
Expand All @@ -38,11 +39,12 @@ export function useRouteHistory(

const callbackUrl = useCallback(
(transformFn?: TransformRouteFn): string | undefined => {
return (
transformFn?.(historyRef.current) || getHistoryAt(historyRef.current, 1)
);
if (transformFn) {
return transformFn(historyRef.current) || rootPath;
}
return getHistoryAt(historyRef.current, 1) || rootPath;
},
[]
[rootPath]
);

const goBack = useCallback(
Expand Down
11 changes: 11 additions & 0 deletions src/hooks/useRouteRoot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useRouter } from "next/router";
import { useMemo } from "react";
import { useConfig } from "./useConfig";

export function useRouteRoot(): string {
const {
config: { redirectRootToPath: path },
} = useConfig();
const { basePath } = useRouter();
return useMemo(() => `${basePath}${path}`, [basePath, path]);
}
40 changes: 0 additions & 40 deletions src/hooks/useSessionTimeout.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/providers/googleSignInAuthentication/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useCredentialsReducer } from "../../hooks/authentication/credentials/us
import { useSessionActive } from "../../hooks/authentication/session/useSessionActive";
import { useSessionAuth } from "../../hooks/authentication/session/useSessionAuth";
import { useSessionCallbackUrl } from "../../hooks/authentication/session/useSessionCallbackUrl";
import { useSessionTimeout } from "../../hooks/authentication/session/useSessionTimeout";
import { useSessionIdleTimer } from "../../hooks/authentication/session/useSessionIdleTimer";
import { useTokenReducer } from "../../hooks/authentication/token/useTokenReducer";
import { AuthContext } from "../authentication/auth/context";
import { AuthenticationContext } from "../authentication/authentication/context";
Expand All @@ -32,7 +32,7 @@ export function GoogleSignInAuthenticationProvider({
const { authDispatch, authState } = authReducer;
const { isAuthenticated } = authState;
useSessionActive(authState);
useSessionTimeout({
useSessionIdleTimer({
disabled: !isAuthenticated,
onIdle: () => service.requestLogout({ callbackUrl }),
timeout,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const service = {
callback: (response: TokenSetParameters) => {
const { id, profile, userinfo } = provider;
const { access_token: token } = response;
dispatch.authenticationDispatch?.(requestAuthentication());
dispatch.tokenDispatch?.(updateToken({ providerId: id, token }));
fetchProfile(userinfo, getAuthenticationRequestOptions(token), {
onError: () =>
Expand All @@ -48,7 +49,6 @@ export const service = {
client_id: provider.clientId,
scope: provider.authorization.params.scope,
});
dispatch.authenticationDispatch?.(requestAuthentication());
client.requestAccessToken();
},
logout: (dispatch: SessionDispatch): void => {
Expand Down
4 changes: 2 additions & 2 deletions src/providers/nextAuthAuthentication/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useAuthReducer } from "../../hooks/authentication/auth/useAuthReducer";
import { useAuthenticationReducer } from "../../hooks/authentication/authentication/useAuthenticationReducer";
import { useSessionAuth } from "../../hooks/authentication/session/useSessionAuth";
import { useSessionCallbackUrl } from "../../hooks/authentication/session/useSessionCallbackUrl";
import { useSessionTimeout } from "../../hooks/authentication/session/useSessionTimeout";
import { useSessionIdleTimer } from "../../hooks/authentication/session/useSessionIdleTimer";
import { AuthContext } from "../authentication/auth/context";
import { AuthenticationContext } from "../authentication/authentication/context";
import { useNextAuthService } from "./hooks/useNextAuthService";
Expand All @@ -22,7 +22,7 @@ export function NextAuthAuthenticationProvider({
const { authDispatch, authState } = authReducer;
const { isAuthenticated } = authState;
const { callbackUrl } = useSessionCallbackUrl();
useSessionTimeout({
useSessionIdleTimer({
disabled: !isAuthenticated,
onIdle: () => {
service.requestLogout({ callbackUrl, redirect: true });
Expand Down

0 comments on commit ae467ab

Please sign in to comment.