fix(dashboard-v2): fix race conditions on /login & /logout calls
This commit is contained in:
parent
8cdf0f86b2
commit
d3c252a0b0
|
@ -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>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Reference in New Issue