From d3c252a0b04d481e4e555bb8e0d37a6c2fc2e74d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Leszczyk?= Date: Tue, 29 Mar 2022 16:08:26 +0200 Subject: [PATCH] fix(dashboard-v2): fix race conditions on /login & /logout calls --- .../src/components/NavBar/NavBar.js | 103 ++++++++++-------- .../src/contexts/user/UserProvider.js | 4 +- packages/dashboard-v2/src/lib/swrConfig.js | 11 +- packages/dashboard-v2/src/pages/auth/login.js | 16 ++- 4 files changed, 81 insertions(+), 53 deletions(-) diff --git a/packages/dashboard-v2/src/components/NavBar/NavBar.js b/packages/dashboard-v2/src/components/NavBar/NavBar.js index 65d9afe5..f75030bb 100644 --- a/packages/dashboard-v2/src/components/NavBar/NavBar.js +++ b/packages/dashboard-v2/src/components/NavBar/NavBar.js @@ -2,6 +2,7 @@ import { Link, navigate } from "gatsby"; import styled from "styled-components"; import { screen } from "../../lib/cssHelpers"; +import { useUser } from "../../contexts/user"; import { DropdownMenu, DropdownMenuLink } from "../DropdownMenu"; import { CogIcon, LockClosedIcon, SkynetLogoIcon } from "../Icons"; import { PageContainer } from "../PageContainer"; @@ -49,48 +50,60 @@ const NavBarBody = styled.nav.attrs({ } `; -export const NavBar = () => ( - - - - - - - - - Dashboard - - - Files - - - Payments - - - - - - { - await accountsService.post("logout"); - navigate("/auth/login"); - // TODO: handle errors - }} - activeClassName="text-primary" - className="cursor-pointer" - icon={LockClosedIcon} - label="Log out" - /> - - - - - -); +export const NavBar = () => { + const { mutate: setUserState } = useUser(); + + const onLogout = async () => { + try { + await accountsService.post("logout"); + // Don't refresh user state from server, as it will now respond with UNAUTHORIZED + // and user will be redirected to /auth/login with return_to query param (which we want empty). + await setUserState(null, { revalidate: false }); + navigate("/auth/login"); + } catch { + // Do nothing. + } + }; + + return ( + + + + + + + + + Dashboard + + + Files + + + Payments + + + + + + + + + + + + ); +}; diff --git a/packages/dashboard-v2/src/contexts/user/UserProvider.js b/packages/dashboard-v2/src/contexts/user/UserProvider.js index 4d1efac5..bb10ffe4 100644 --- a/packages/dashboard-v2/src/contexts/user/UserProvider.js +++ b/packages/dashboard-v2/src/contexts/user/UserProvider.js @@ -1,10 +1,10 @@ import { useEffect, useState } from "react"; -import useSWR from "swr"; +import useSWRImmutable from "swr/immutable"; import { UserContext } from "./UserContext"; export const UserProvider = ({ children }) => { - const { data: user, error, mutate } = useSWR("user"); + const { data: user, error, mutate } = useSWRImmutable("user"); const [loading, setLoading] = useState(true); useEffect(() => { diff --git a/packages/dashboard-v2/src/lib/swrConfig.js b/packages/dashboard-v2/src/lib/swrConfig.js index 058b5ead..16e2dbfb 100644 --- a/packages/dashboard-v2/src/lib/swrConfig.js +++ b/packages/dashboard-v2/src/lib/swrConfig.js @@ -15,12 +15,15 @@ const redirectUnauthenticated = (key) => }); const redirectAuthenticated = (key) => - fetch(`${baseUrl}/${key}`).then((response) => { - if (response.status === StatusCodes.OK) { - navigate(`/`); + fetch(`${baseUrl}/${key}`).then(async (response) => { + if (response.ok) { + await navigate("/"); + return response.json(); } - return response.json(); + // If there was an error, let's throw so useSWR's "error" property is populated instead "data". + const data = await response.json(); + throw new Error(data?.message || `Error occured when trying to fetch: ${key}`); }); export const allUsers = { diff --git a/packages/dashboard-v2/src/pages/auth/login.js b/packages/dashboard-v2/src/pages/auth/login.js index e3b5240b..8b0cfbb5 100644 --- a/packages/dashboard-v2/src/pages/auth/login.js +++ b/packages/dashboard-v2/src/pages/auth/login.js @@ -1,19 +1,31 @@ +import { useEffect } from "react"; import { navigate } from "gatsby"; import AuthLayout from "../../layouts/AuthLayout"; - import { LoginForm } from "../../components/forms"; +import { useUser } from "../../contexts/user"; const LoginPage = ({ location }) => { + const { user, mutate: refreshUserState } = useUser(); const query = new URLSearchParams(location.search); const redirectTo = query.get("return_to"); + useEffect(() => { + if (user) { + navigate(redirectTo || "/"); + } + }, [user, redirectTo]); + return (
Skynet
- navigate(redirectTo || "/")} /> + { + await refreshUserState(); + }} + />
); };