fix(dashboard-v2): fix race conditions on /login & /logout calls

This commit is contained in:
Michał Leszczyk 2022-03-29 16:08:26 +02:00
parent 8cdf0f86b2
commit d3c252a0b0
No known key found for this signature in database
GPG Key ID: FA123CA8BAA2FBF4
4 changed files with 81 additions and 53 deletions

View File

@ -2,6 +2,7 @@ import { Link, navigate } from "gatsby";
import styled from "styled-components"; import styled from "styled-components";
import { screen } from "../../lib/cssHelpers"; import { screen } from "../../lib/cssHelpers";
import { useUser } from "../../contexts/user";
import { DropdownMenu, DropdownMenuLink } from "../DropdownMenu"; import { DropdownMenu, DropdownMenuLink } from "../DropdownMenu";
import { CogIcon, LockClosedIcon, SkynetLogoIcon } from "../Icons"; import { CogIcon, LockClosedIcon, SkynetLogoIcon } from "../Icons";
import { PageContainer } from "../PageContainer"; import { PageContainer } from "../PageContainer";
@ -49,7 +50,22 @@ const NavBarBody = styled.nav.attrs({
} }
`; `;
export const NavBar = () => ( 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 (
<NavBarContainer> <NavBarContainer>
<PageContainer className="px-0"> <PageContainer className="px-0">
<NavBarBody> <NavBarBody>
@ -78,11 +94,7 @@ export const NavBar = () => (
partiallyActive partiallyActive
/> />
<DropdownMenuLink <DropdownMenuLink
onClick={async () => { onClick={onLogout}
await accountsService.post("logout");
navigate("/auth/login");
// TODO: handle errors
}}
activeClassName="text-primary" activeClassName="text-primary"
className="cursor-pointer" className="cursor-pointer"
icon={LockClosedIcon} icon={LockClosedIcon}
@ -93,4 +105,5 @@ export const NavBar = () => (
</NavBarBody> </NavBarBody>
</PageContainer> </PageContainer>
</NavBarContainer> </NavBarContainer>
); );
};

View File

@ -1,10 +1,10 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import useSWR from "swr"; import useSWRImmutable from "swr/immutable";
import { UserContext } from "./UserContext"; import { UserContext } from "./UserContext";
export const UserProvider = ({ children }) => { export const UserProvider = ({ children }) => {
const { data: user, error, mutate } = useSWR("user"); const { data: user, error, mutate } = useSWRImmutable("user");
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {

View File

@ -15,12 +15,15 @@ const redirectUnauthenticated = (key) =>
}); });
const redirectAuthenticated = (key) => const redirectAuthenticated = (key) =>
fetch(`${baseUrl}/${key}`).then((response) => { fetch(`${baseUrl}/${key}`).then(async (response) => {
if (response.status === StatusCodes.OK) { if (response.ok) {
navigate(`/`); 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 = { export const allUsers = {

View File

@ -1,19 +1,31 @@
import { useEffect } from "react";
import { navigate } from "gatsby"; import { navigate } from "gatsby";
import AuthLayout from "../../layouts/AuthLayout"; import AuthLayout from "../../layouts/AuthLayout";
import { LoginForm } from "../../components/forms"; import { LoginForm } from "../../components/forms";
import { useUser } from "../../contexts/user";
const LoginPage = ({ location }) => { const LoginPage = ({ location }) => {
const { user, mutate: refreshUserState } = useUser();
const query = new URLSearchParams(location.search); const query = new URLSearchParams(location.search);
const redirectTo = query.get("return_to"); const redirectTo = query.get("return_to");
useEffect(() => {
if (user) {
navigate(redirectTo || "/");
}
}, [user, redirectTo]);
return ( return (
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen"> <div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
<div className="mb-4 md:mb-16"> <div className="mb-4 md:mb-16">
<img src="/images/logo-black-text.svg" alt="Skynet" /> <img src="/images/logo-black-text.svg" alt="Skynet" />
</div> </div>
<LoginForm onSuccess={() => navigate(redirectTo || "/")} /> <LoginForm
onSuccess={async () => {
await refreshUserState();
}}
/>
</div> </div>
); );
}; };