diff --git a/src/Common/hooks/useAuthUser.ts b/src/Common/hooks/useAuthUser.ts index 6dd383f5224..dda5f399952 100644 --- a/src/Common/hooks/useAuthUser.ts +++ b/src/Common/hooks/useAuthUser.ts @@ -16,11 +16,17 @@ export const AuthUserContext = createContext(null); export const useAuthContext = () => { const ctx = useContext(AuthUserContext); if (!ctx) { - throw new Error("useAuthUser must be used within an AuthUserProvider"); + throw new Error( + "'useAuthContext' must be used within 'AuthUserProvider' only" + ); } return ctx; }; export default function useAuthUser() { - return useAuthContext().user; + const user = useAuthContext().user; + if (!user) { + throw new Error("'useAuthUser' must be used within 'AppRouter' only"); + } + return user; } diff --git a/src/Components/Common/Sidebar/SidebarUserCard.tsx b/src/Components/Common/Sidebar/SidebarUserCard.tsx index 59970e8a73c..75cf2d9ce43 100644 --- a/src/Components/Common/Sidebar/SidebarUserCard.tsx +++ b/src/Components/Common/Sidebar/SidebarUserCard.tsx @@ -1,13 +1,13 @@ import { Link } from "raviger"; import { useTranslation } from "react-i18next"; import CareIcon from "../../../CAREUI/icons/CareIcon"; -import { handleSignOut } from "../../../Utils/utils"; -import useAuthUser from "../../../Common/hooks/useAuthUser"; +import { formatName } from "../../../Utils/utils"; +import useAuthUser, { useAuthContext } from "../../../Common/hooks/useAuthUser"; const SidebarUserCard = ({ shrinked }: { shrinked: boolean }) => { const { t } = useTranslation(); const user = useAuthUser(); - const profileName = `${user.first_name ?? ""} ${user.last_name ?? ""}`.trim(); + const { signOut } = useAuthContext(); return (
{ -
handleSignOut(true)} - > +
{ className="flex-nowrap overflow-hidden break-words font-semibold text-white" id="profilenamelink" > - {profileName} + {formatName(user)}
handleSignOut(true)} + onClick={signOut} >
{ - handleSignOut(false); - }} + onClick={signOut} className="hover:bg-primary- inline-block cursor-pointer rounded-lg bg-primary-600 px-4 py-2 text-white hover:text-white" > {t("return_to_login")} diff --git a/src/Components/Users/UserProfile.tsx b/src/Components/Users/UserProfile.tsx index 76a94745c1a..2dc75d33613 100644 --- a/src/Components/Users/UserProfile.tsx +++ b/src/Components/Users/UserProfile.tsx @@ -5,7 +5,7 @@ import * as Notification from "../../Utils/Notifications.js"; import LanguageSelector from "../../Components/Common/LanguageSelector"; import TextFormField from "../Form/FormFields/TextFormField"; import ButtonV2, { Submit } from "../Common/components/ButtonV2"; -import { classNames, handleSignOut, parsePhoneNumber } from "../../Utils/utils"; +import { classNames, parsePhoneNumber } from "../../Utils/utils"; import CareIcon from "../../CAREUI/icons/CareIcon"; import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField"; import { FieldChangeEvent } from "../Form/FormFields/Utils"; @@ -13,7 +13,7 @@ import { SelectFormField } from "../Form/FormFields/SelectFormField"; import { GenderType, SkillModel, UpdatePasswordForm } from "../Users/models"; import UpdatableApp, { checkForUpdate } from "../Common/UpdatableApp"; import dayjs from "../../Utils/dayjs"; -import useAuthUser from "../../Common/hooks/useAuthUser"; +import useAuthUser, { useAuthContext } from "../../Common/hooks/useAuthUser"; import { PhoneNumberValidator } from "../Form/FieldValidators"; import useQuery from "../../Utils/request/useQuery"; import routes from "../../Redux/api"; @@ -100,6 +100,7 @@ const editFormReducer = (state: State, action: Action) => { }; export default function UserProfile() { + const { signOut } = useAuthContext(); const [states, dispatch] = useReducer(editFormReducer, initialState); const [updateStatus, setUpdateStatus] = useState({ isChecking: false, @@ -413,7 +414,7 @@ export default function UserProfile() { > {showEdit ? "Cancel" : "Edit User Profile"} - handleSignOut(true)}> + Sign out diff --git a/src/Providers/AuthUserProvider.tsx b/src/Providers/AuthUserProvider.tsx index 467fe9e9b8b..5435b0f0b45 100644 --- a/src/Providers/AuthUserProvider.tsx +++ b/src/Providers/AuthUserProvider.tsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useCallback, useEffect } from "react"; import { AuthUserContext } from "../Common/hooks/useAuthUser"; import Loading from "../Components/Common/Loading"; import routes from "../Redux/api"; @@ -33,34 +33,55 @@ export default function AuthUserProvider({ children, unauthorized }: Props) { setInterval(() => updateRefreshToken(), tokenRefreshInterval); }, [user, tokenRefreshInterval]); - if (loading || !res) { - return ; - } + const signIn = useCallback( + async (creds: { username: string; password: string }) => { + const query = await request(routes.login, { body: creds }); - const signIn = async (creds: { username: string; password: string }) => { - const query = await request(routes.login, { body: creds }); + if (query.res?.ok && query.data) { + localStorage.setItem(LocalStorageKeys.accessToken, query.data.access); + localStorage.setItem(LocalStorageKeys.refreshToken, query.data.refresh); - if (query.res?.ok && query.data) { - localStorage.setItem(LocalStorageKeys.accessToken, query.data.access); - localStorage.setItem(LocalStorageKeys.refreshToken, query.data.refresh); - - await refetch(); - navigate(getRedirectOr("/")); - } + await refetch(); + navigate(getRedirectOr("/")); + } - return query; - }; + return query; + }, + [refetch] + ); - const signOut = async () => { + const signOut = useCallback(async () => { localStorage.removeItem(LocalStorageKeys.accessToken); localStorage.removeItem(LocalStorageKeys.refreshToken); - const redirectURL = getRedirectURL(); - await refetch(); + const redirectURL = getRedirectURL(); navigate(redirectURL ? `/?redirect=${redirectURL}` : "/"); - }; + }, [refetch]); + + // Handles signout from current tab, if signed out from another tab. + useEffect(() => { + const listener = (event: any) => { + if ( + !event.newValue && + (LocalStorageKeys.accessToken === event.key || + LocalStorageKeys.refreshToken === event.key) + ) { + signOut(); + } + }; + + addEventListener("storage", listener); + + return () => { + removeEventListener("storage", listener); + }; + }, [signOut]); + + if (loading || !res) { + return ; + } return ( diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index d098a480149..5e238627bc8 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -10,9 +10,8 @@ import { SIDEBAR_SHRINK_PREFERENCE_KEY, SidebarShrinkContext, } from "../Components/Common/Sidebar/Sidebar"; -import { BLACKLISTED_PATHS, LocalStorageKeys } from "../Common/constants"; +import { BLACKLISTED_PATHS } from "../Common/constants"; import useConfig from "../Common/hooks/useConfig"; -import { handleSignOut } from "../Utils/utils"; import SessionExpired from "../Components/ErrorPages/SessionExpired"; import UserRoutes from "./routes/UserRoutes"; @@ -63,19 +62,6 @@ export default function AppRouter() { const path = usePath(); const [sidebarOpen, setSidebarOpen] = useState(false); - useEffect(() => { - addEventListener("storage", (event: any) => { - if ( - [LocalStorageKeys.accessToken, LocalStorageKeys.refreshToken].includes( - event.key - ) && - !event.newValue - ) { - handleSignOut(true); - } - }); - }, []); - useEffect(() => { setSidebarOpen(false); let flag = false; diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index e85e8158158..74c63e71a85 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -1,9 +1,4 @@ -import { navigate } from "raviger"; -import { - AREACODES, - IN_LANDLINE_AREA_CODES, - LocalStorageKeys, -} from "../Common/constants"; +import { AREACODES, IN_LANDLINE_AREA_CODES } from "../Common/constants"; import phoneCodesJson from "../Common/static/countryPhoneAndFlags.json"; import dayjs from "./dayjs"; @@ -119,21 +114,6 @@ export const dateQueryString = (date: DateLike) => { export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); -export const handleSignOut = (forceReload: boolean) => { - Object.values(LocalStorageKeys).forEach((key) => - localStorage.removeItem(key) - ); - const redirectURL = new URLSearchParams(window.location.search).get( - "redirect" - ); - const url = redirectURL ? `/?redirect=${redirectURL}` : "/"; - if (forceReload) { - window.location.href = url; - } else { - navigate(url); - } -}; - /** * Referred from: https://stackoverflow.com/a/9039885/7887936 * @returns `true` if device is iOS, else `false`