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 { 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 = () => (
<NavBarContainer>
<PageContainer className="px-0">
<NavBarBody>
<NavBarSection className="logo-area pl-2 pr-4 md:px-0 md:w-[110px] justify-center sm:justify-start">
<SkynetLogoIcon size={48} />
</NavBarSection>
<NavBarSection className="navigation-area border-t border-palette-100">
<NavBarLink to="/" as={Link} activeClassName="!border-b-primary">
Dashboard
</NavBarLink>
<NavBarLink to="/files" as={Link} activeClassName="!border-b-primary">
Files
</NavBarLink>
<NavBarLink to="/payments" as={Link} activeClassName="!border-b-primary">
Payments
</NavBarLink>
</NavBarSection>
<NavBarSection className="dropdown-area justify-end">
<DropdownMenu title="My account">
<DropdownMenuLink
to="/settings"
as={Link}
activeClassName="text-primary"
icon={CogIcon}
label="Settings"
partiallyActive
/>
<DropdownMenuLink
onClick={async () => {
await accountsService.post("logout");
navigate("/auth/login");
// TODO: handle errors
}}
activeClassName="text-primary"
className="cursor-pointer"
icon={LockClosedIcon}
label="Log out"
/>
</DropdownMenu>
</NavBarSection>
</NavBarBody>
</PageContainer>
</NavBarContainer>
);
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>
<PageContainer className="px-0">
<NavBarBody>
<NavBarSection className="logo-area pl-2 pr-4 md:px-0 md:w-[110px] justify-center sm:justify-start">
<SkynetLogoIcon size={48} />
</NavBarSection>
<NavBarSection className="navigation-area border-t border-palette-100">
<NavBarLink to="/" as={Link} activeClassName="!border-b-primary">
Dashboard
</NavBarLink>
<NavBarLink to="/files" as={Link} activeClassName="!border-b-primary">
Files
</NavBarLink>
<NavBarLink to="/payments" as={Link} activeClassName="!border-b-primary">
Payments
</NavBarLink>
</NavBarSection>
<NavBarSection className="dropdown-area justify-end">
<DropdownMenu title="My account">
<DropdownMenuLink
to="/settings"
as={Link}
activeClassName="text-primary"
icon={CogIcon}
label="Settings"
partiallyActive
/>
<DropdownMenuLink
onClick={onLogout}
activeClassName="text-primary"
className="cursor-pointer"
icon={LockClosedIcon}
label="Log out"
/>
</DropdownMenu>
</NavBarSection>
</NavBarBody>
</PageContainer>
</NavBarContainer>
);
};

View File

@ -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(() => {

View File

@ -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 = {

View File

@ -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 (
<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">
<img src="/images/logo-black-text.svg" alt="Skynet" />
</div>
<LoginForm onSuccess={() => navigate(redirectTo || "/")} />
<LoginForm
onSuccess={async () => {
await refreshUserState();
}}
/>
</div>
);
};