Use compute auth service and fix static code analyzer warnings (#15)
* Clean up formatting * Use new compute_auth service Implment SSO Implement token refresh Clean up unit tests * Fix unit tests * Fix auth test Fix warnings * Update default settings for compute_auth
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Typography } from "@material-ui/core";
|
||||
import React from "react";
|
||||
import useStyles from '../Styles';
|
||||
import useStyles from "../useStyles";
|
||||
|
||||
const PageNotFound = () => {
|
||||
const classes = useStyles();
|
||||
@@ -12,6 +12,6 @@ const PageNotFound = () => {
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default PageNotFound;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
jest.mock("../Contexts/UserContext");
|
||||
jest.mock("../Contexts/FileUploadContext");
|
||||
|
||||
import { render, screen, cleanup, waitForElementToBeRemoved, waitFor } from "@testing-library/react"
|
||||
import { render, screen, cleanup, waitForElementToBeRemoved } from "@testing-library/react";
|
||||
import { setToken } from "../Contexts/UserContext";
|
||||
import App from ".";
|
||||
|
||||
const TEST_TOKEN = { idToken: { jwtToken: "TEST" }};
|
||||
const TEST_TOKEN = { idToken: { jwtToken: "TEST" } };
|
||||
const LOADING_STATUS = "Loading...";
|
||||
|
||||
const renderRoute = async (route) => {
|
||||
@@ -26,19 +26,13 @@ describe("App", () => {
|
||||
|
||||
it("Route / unauthenticated", async () => {
|
||||
const container = await renderRoute("/");
|
||||
expect(container.querySelector("h1").innerHTML).toEqual("Sign in");
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("Route /signup unauthenticated", async () => {
|
||||
const container = await renderRoute("/signup");
|
||||
expect(container.querySelector("h1").innerHTML).toEqual("Sign up");
|
||||
expect(container.querySelector("h1").innerHTML).toEqual("Fisker OTA Portal");
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("Route /home unauthenticated", async () => {
|
||||
const container = await renderRoute("/home");
|
||||
expect(container.querySelector("h1").innerHTML).toEqual("Sign in");
|
||||
expect(container.querySelector("h1").innerHTML).toEqual("Fisker OTA Portal");
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -49,13 +43,6 @@ describe("App", () => {
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("Route /signup authenticated", async () => {
|
||||
setToken(TEST_TOKEN);
|
||||
const container = await renderRoute("/signup");
|
||||
expect(container.querySelector("h1").innerHTML).toEqual("Upload file");
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("Route /home authenticated", async () => {
|
||||
setToken(TEST_TOKEN);
|
||||
const container = await renderRoute("/home");
|
||||
|
||||
@@ -9,7 +9,7 @@ exports[`App Route / authenticated 1`] = `
|
||||
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-paper-25"
|
||||
class="makeStyles-paper-9"
|
||||
>
|
||||
<h1
|
||||
class="MuiTypography-root MuiTypography-h5"
|
||||
@@ -20,7 +20,7 @@ exports[`App Route / authenticated 1`] = `
|
||||
data-testid="mocked-fileuploadprovider"
|
||||
>
|
||||
<form
|
||||
class="makeStyles-form-27"
|
||||
class="makeStyles-form-11"
|
||||
novalidate=""
|
||||
>
|
||||
<div
|
||||
@@ -98,109 +98,18 @@ exports[`App Route / unauthenticated 1`] = `
|
||||
<h1
|
||||
class="MuiTypography-root MuiTypography-h5"
|
||||
>
|
||||
Sign in
|
||||
Fisker OTA Portal
|
||||
</h1>
|
||||
<form
|
||||
action="{onSubmit}"
|
||||
class="makeStyles-form-3"
|
||||
novalidate=""
|
||||
>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused Mui-required Mui-required"
|
||||
data-shrink="true"
|
||||
for="email"
|
||||
id="email-label"
|
||||
>
|
||||
Email Address
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||
>
|
||||
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
autocomplete="email"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||
id="email"
|
||||
name="email"
|
||||
required=""
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-7 PrivateNotchedOutline-legendNotched-8"
|
||||
>
|
||||
<span>
|
||||
Email Address
|
||||
*
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||
data-shrink="false"
|
||||
for="password"
|
||||
id="password-label"
|
||||
>
|
||||
Password
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||
>
|
||||
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
autocomplete="current-password"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||
id="password"
|
||||
name="password"
|
||||
required=""
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-7"
|
||||
>
|
||||
<span>
|
||||
Password
|
||||
*
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
<a
|
||||
aria-disabled="false"
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-4 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||
href="https://cognito.com/authorize?redirect=https://example.com/callback"
|
||||
tabindex="0"
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label"
|
||||
@@ -210,21 +119,7 @@ exports[`App Route / unauthenticated 1`] = `
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-container"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-body2 MuiTypography-colorPrimary"
|
||||
href="/signup"
|
||||
>
|
||||
Don't have an account? Sign Up
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
@@ -241,7 +136,7 @@ exports[`App Route /home authenticated 1`] = `
|
||||
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-paper-33"
|
||||
class="makeStyles-paper-13"
|
||||
>
|
||||
<h1
|
||||
class="MuiTypography-root MuiTypography-h5"
|
||||
@@ -252,7 +147,7 @@ exports[`App Route /home authenticated 1`] = `
|
||||
data-testid="mocked-fileuploadprovider"
|
||||
>
|
||||
<form
|
||||
class="makeStyles-form-35"
|
||||
class="makeStyles-form-15"
|
||||
novalidate=""
|
||||
>
|
||||
<div
|
||||
@@ -325,114 +220,23 @@ exports[`App Route /home unauthenticated 1`] = `
|
||||
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-paper-17"
|
||||
class="makeStyles-paper-5"
|
||||
>
|
||||
<h1
|
||||
class="MuiTypography-root MuiTypography-h5"
|
||||
>
|
||||
Sign in
|
||||
Fisker OTA Portal
|
||||
</h1>
|
||||
<form
|
||||
action="{onSubmit}"
|
||||
class="makeStyles-form-19"
|
||||
class="makeStyles-form-7"
|
||||
novalidate=""
|
||||
>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused Mui-required Mui-required"
|
||||
data-shrink="true"
|
||||
for="email"
|
||||
id="email-label"
|
||||
>
|
||||
Email Address
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||
>
|
||||
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
autocomplete="email"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||
id="email"
|
||||
name="email"
|
||||
required=""
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-21 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-23 PrivateNotchedOutline-legendNotched-24"
|
||||
>
|
||||
<span>
|
||||
Email Address
|
||||
*
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||
data-shrink="false"
|
||||
for="password"
|
||||
id="password-label"
|
||||
>
|
||||
Password
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||
>
|
||||
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
autocomplete="current-password"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||
id="password"
|
||||
name="password"
|
||||
required=""
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-21 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-23"
|
||||
>
|
||||
<span>
|
||||
Password
|
||||
*
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-20 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||
<a
|
||||
aria-disabled="false"
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-8 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||
href="https://cognito.com/authorize?redirect=https://example.com/callback"
|
||||
tabindex="0"
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label"
|
||||
@@ -442,21 +246,7 @@ exports[`App Route /home unauthenticated 1`] = `
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-container"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-body2 MuiTypography-colorPrimary"
|
||||
href="/signup"
|
||||
>
|
||||
Don't have an account? Sign Up
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
@@ -470,7 +260,7 @@ exports[`App Route /page-not-found authenticated 1`] = `
|
||||
data-testid="mocked-userprovider"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-paper-41"
|
||||
class="makeStyles-paper-21"
|
||||
>
|
||||
<h1
|
||||
class="MuiTypography-root MuiTypography-h2"
|
||||
@@ -488,7 +278,7 @@ exports[`App Route /page-not-found unauthenticated 1`] = `
|
||||
data-testid="mocked-userprovider"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-paper-37"
|
||||
class="makeStyles-paper-17"
|
||||
>
|
||||
<h1
|
||||
class="MuiTypography-root MuiTypography-h2"
|
||||
@@ -499,279 +289,3 @@ exports[`App Route /page-not-found unauthenticated 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`App Route /signup authenticated 1`] = `
|
||||
<div>
|
||||
<div
|
||||
data-testid="mocked-userprovider"
|
||||
>
|
||||
<main
|
||||
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-paper-29"
|
||||
>
|
||||
<h1
|
||||
class="MuiTypography-root MuiTypography-h5"
|
||||
>
|
||||
Upload file
|
||||
</h1>
|
||||
<div
|
||||
data-testid="mocked-fileuploadprovider"
|
||||
>
|
||||
<form
|
||||
class="makeStyles-form-31"
|
||||
novalidate=""
|
||||
>
|
||||
<div
|
||||
class="MuiDropzoneArea-root"
|
||||
tabindex="0"
|
||||
>
|
||||
<input
|
||||
accept=""
|
||||
autocomplete="off"
|
||||
style="display: none;"
|
||||
tabindex="-1"
|
||||
type="file"
|
||||
/>
|
||||
<div
|
||||
class="MuiDropzoneArea-textContainer"
|
||||
>
|
||||
<p
|
||||
class="MuiTypography-root MuiDropzoneArea-text MuiTypography-h5"
|
||||
>
|
||||
Drag and drop a file here or click
|
||||
</p>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root MuiDropzoneArea-icon"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-container"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item"
|
||||
>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-text"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label"
|
||||
>
|
||||
Sign Out
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`App Route /signup unauthenticated 1`] = `
|
||||
<div>
|
||||
<div
|
||||
data-testid="mocked-userprovider"
|
||||
>
|
||||
<main
|
||||
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-paper-9"
|
||||
>
|
||||
<h1
|
||||
class="MuiTypography-root MuiTypography-h5"
|
||||
>
|
||||
Sign up
|
||||
</h1>
|
||||
<form
|
||||
class="makeStyles-form-11"
|
||||
novalidate=""
|
||||
>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused Mui-required Mui-required"
|
||||
data-shrink="true"
|
||||
for="email"
|
||||
id="email-label"
|
||||
>
|
||||
Email Address
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||
>
|
||||
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
autocomplete="email"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||
id="email"
|
||||
name="email"
|
||||
required=""
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-13 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-15 PrivateNotchedOutline-legendNotched-16"
|
||||
>
|
||||
<span>
|
||||
Email Address
|
||||
*
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||
data-shrink="false"
|
||||
for="password"
|
||||
id="password-label"
|
||||
>
|
||||
Password
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||
>
|
||||
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
autocomplete="new-password"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||
id="password"
|
||||
name="password"
|
||||
required=""
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-13 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-15"
|
||||
>
|
||||
<span>
|
||||
Password
|
||||
*
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||
data-shrink="false"
|
||||
for="passwordConfirm"
|
||||
id="passwordConfirm-label"
|
||||
>
|
||||
Confirm Password
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||
>
|
||||
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||
id="passwordConfirm"
|
||||
name="password"
|
||||
required=""
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-13 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-15"
|
||||
>
|
||||
<span>
|
||||
Confirm Password
|
||||
*
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-12 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||
tabindex="0"
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label"
|
||||
>
|
||||
Sign Up
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-container"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-body2 MuiTypography-colorPrimary"
|
||||
href="/"
|
||||
>
|
||||
Already have an account? Sign In
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { UserProvider } from '../Contexts/UserContext';
|
||||
import SiteRoutes from '../Routes/SiteRoutes';
|
||||
import React from "react";
|
||||
import { UserProvider } from "../Contexts/UserContext";
|
||||
import SiteRoutes from "../Routes/SiteRoutes";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
|
||||
@@ -26,26 +26,36 @@ export const FileUploadProvider = ({ children }) => {
|
||||
|
||||
const upload = async (files, accessToken) => {
|
||||
try {
|
||||
if (!files || files.length === 0) throw new Error("File required");
|
||||
if (!accessToken || accessToken.length === 0) throw new Error("Access token required")
|
||||
if (!files || files.length === 0) {
|
||||
throw new Error("File required");
|
||||
}
|
||||
if (!accessToken || accessToken.length === 0) {
|
||||
throw new Error("Access token required");
|
||||
}
|
||||
const file = files[0].file;
|
||||
const filename = file.name;
|
||||
|
||||
|
||||
setUploading(true);
|
||||
setLinkURL(null);
|
||||
setProgress(0);
|
||||
setStatus(`Uploading ${filename}`);
|
||||
setCancelUpload(getCancelToken());
|
||||
|
||||
const { data } = await uploadFile(file, accessToken, setProgress, cancelUpload);
|
||||
if (data.message) throw new Error(`${data.error}. ${data.message}`);
|
||||
const url = ((data && data.link) ? data.link : "No URL available");
|
||||
|
||||
const { data } = await uploadFile(
|
||||
file,
|
||||
accessToken,
|
||||
setProgress,
|
||||
cancelUpload
|
||||
);
|
||||
if (data.message) {
|
||||
throw new Error(`${data.error}. ${data.message}`);
|
||||
}
|
||||
const url = data && data.link ? data.link : "No URL available";
|
||||
setLinkURL(url);
|
||||
setStatus(`Uploaded ${filename}`);
|
||||
setCancelUpload(null);
|
||||
setProgress(100);
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
setStatus(`Error occured: ${e.message}`);
|
||||
setProgress(-1);
|
||||
}
|
||||
@@ -59,15 +69,17 @@ export const FileUploadProvider = ({ children }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<FileUploadContext.Provider value={{
|
||||
uploading,
|
||||
progress,
|
||||
status,
|
||||
linkURL,
|
||||
upload,
|
||||
cancel,
|
||||
rejectedFile,
|
||||
}}>
|
||||
<FileUploadContext.Provider
|
||||
value={{
|
||||
uploading,
|
||||
progress,
|
||||
status,
|
||||
linkURL,
|
||||
upload,
|
||||
cancel,
|
||||
rejectedFile,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</FileUploadContext.Provider>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,37 @@
|
||||
jest.mock("../../services/uploadFile");
|
||||
|
||||
import { setUploadFileDelay } from "../../services/uploadFile"
|
||||
import { FileUploadProvider, useFileUploadContext } from "../Contexts/FileUploadContext";
|
||||
import {render, cleanup, screen, fireEvent, waitFor} from "@testing-library/react"
|
||||
import { setUploadFileDelay } from "../../services/uploadFile";
|
||||
import {
|
||||
FileUploadProvider,
|
||||
useFileUploadContext,
|
||||
} from "../Contexts/FileUploadContext";
|
||||
import {
|
||||
render,
|
||||
cleanup,
|
||||
screen,
|
||||
fireEvent,
|
||||
waitFor,
|
||||
} from "@testing-library/react";
|
||||
|
||||
const checkState = (uploading, progress, status, linkURL) => {
|
||||
expect(screen.getByTestId("uploading").innerHTML).toEqual(uploading);
|
||||
expect(screen.getByTestId("progress").innerHTML).toEqual(progress);
|
||||
expect(screen.getByTestId("status").innerHTML).toEqual(status);
|
||||
expect(screen.getByTestId("linkURL").innerHTML).toEqual(linkURL);
|
||||
};
|
||||
|
||||
describe("FileUploadContext", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
const TestComp = () => {
|
||||
const { progress, uploading, status, linkURL, upload, cancel } = useFileUploadContext();
|
||||
const TEST_FILE = [{ file: { name: "test.jpg", size: 0 }}];
|
||||
const {
|
||||
progress,
|
||||
uploading,
|
||||
status,
|
||||
linkURL,
|
||||
upload,
|
||||
cancel,
|
||||
} = useFileUploadContext();
|
||||
const TEST_FILE = [{ file: { name: "test.jpg", size: 0 } }];
|
||||
const TEST_ACCESSTOKEN = "ACCESSTOKEN";
|
||||
return (
|
||||
<>
|
||||
@@ -17,14 +39,24 @@ describe("FileUploadContext", () => {
|
||||
<div data-testid="progress">{progress.toString()}</div>
|
||||
<div data-testid="status">{status}</div>
|
||||
<div data-testid="linkURL">{linkURL}</div>
|
||||
<button data-testid="uploadNoFile" onClick={() => upload()}/>
|
||||
<button data-testid="uploadNoToken" onClick={() => upload(TEST_FILE)}/>
|
||||
<button data-testid="upload" onClick={() => upload(TEST_FILE, TEST_ACCESSTOKEN)}/>
|
||||
<button data-testid="cancel" onClick={() => cancel()}/>
|
||||
<button data-testid="uploadNoFile" onClick={() => upload()} />
|
||||
<button
|
||||
data-testid="uploadNoToken"
|
||||
onClick={() => upload(TEST_FILE)}
|
||||
/>
|
||||
<button
|
||||
data-testid="upload"
|
||||
onClick={() => upload(TEST_FILE, TEST_ACCESSTOKEN)}
|
||||
/>
|
||||
<button data-testid="cancel" onClick={() => cancel()} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
render(<FileUploadProvider><TestComp /></FileUploadProvider>);
|
||||
render(
|
||||
<FileUploadProvider>
|
||||
<TestComp />
|
||||
</FileUploadProvider>
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -32,46 +64,39 @@ describe("FileUploadContext", () => {
|
||||
});
|
||||
|
||||
it("Initial state", async () => {
|
||||
expect(screen.getByTestId("uploading").innerHTML).toEqual("false");
|
||||
expect(screen.getByTestId("progress").innerHTML).toEqual("0");
|
||||
expect(screen.getByTestId("status").innerHTML).toEqual("");
|
||||
expect(screen.getByTestId("linkURL").innerHTML).toEqual("");
|
||||
})
|
||||
checkState("false", "0", "", "");
|
||||
});
|
||||
|
||||
it("Upload no file", async () => {
|
||||
fireEvent.click(screen.getByTestId("uploadNoFile"));
|
||||
expect(screen.getByTestId("uploading").innerHTML).toEqual("false");
|
||||
expect(screen.getByTestId("progress").innerHTML).toEqual("-1");
|
||||
expect(screen.getByTestId("status").innerHTML).toEqual("Error occured: File required");
|
||||
expect(screen.getByTestId("linkURL").innerHTML).toEqual("");
|
||||
})
|
||||
checkState("false", "-1", "Error occured: File required", "");
|
||||
});
|
||||
|
||||
it("Upload no access token", async () => {
|
||||
fireEvent.click(screen.getByTestId("uploadNoToken"));
|
||||
expect(screen.getByTestId("uploading").innerHTML).toEqual("false");
|
||||
expect(screen.getByTestId("progress").innerHTML).toEqual("-1");
|
||||
expect(screen.getByTestId("status").innerHTML).toEqual("Error occured: Access token required");
|
||||
expect(screen.getByTestId("linkURL").innerHTML).toEqual("");
|
||||
})
|
||||
checkState("false", "-1", "Error occured: Access token required", "");
|
||||
});
|
||||
|
||||
it("Upload file", async () => {
|
||||
fireEvent.click(screen.getByTestId("upload"));
|
||||
await waitFor(() => expect(screen.getByTestId("progress").innerHTML).toEqual("100"));
|
||||
expect(screen.getByTestId("uploading").innerHTML).toEqual("true");
|
||||
expect(screen.getByTestId("status").innerHTML).toEqual(`Uploaded test.jpg`);
|
||||
expect(screen.getByTestId("linkURL").innerHTML).toEqual(`CLOUDFRONT_URL`);
|
||||
})
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("progress").innerHTML).toEqual("100")
|
||||
);
|
||||
checkState("true", "100", "Uploaded test.jpg", "CLOUDFRONT_URL");
|
||||
});
|
||||
|
||||
it("Cancel upload", async () => {
|
||||
setUploadFileDelay(true);
|
||||
fireEvent.click(screen.getByTestId("upload"));
|
||||
await waitFor(() => expect(screen.getByTestId("progress").innerHTML).toEqual("50"));
|
||||
expect(screen.getByTestId("uploading").innerHTML).toEqual("true");
|
||||
expect(screen.getByTestId("status").innerHTML).toEqual("Uploading test.jpg");
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("progress").innerHTML).toEqual("50")
|
||||
);
|
||||
checkState("true", "50", "Uploading test.jpg", "");
|
||||
|
||||
fireEvent.click(screen.getByTestId("cancel"));
|
||||
await waitFor(() => expect(screen.getByTestId("progress").innerHTML).toEqual("0"));
|
||||
expect(screen.getByTestId("uploading").innerHTML).toEqual("false");
|
||||
expect(screen.getByTestId("status").innerHTML).toEqual("Upload cancelled");
|
||||
expect(screen.getByTestId("linkURL").innerHTML).toEqual("");
|
||||
})
|
||||
})
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("progress").innerHTML).toEqual("0")
|
||||
);
|
||||
checkState("false", "0", "Upload cancelled", "");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,98 +17,86 @@ export const UserProvider = ({ children }) => {
|
||||
if (!t.idToken.payload || !t.idToken.payload.exp) return;
|
||||
setToken(t);
|
||||
}, []);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) return;
|
||||
const { idToken: { jwtToken }} = token;
|
||||
const {
|
||||
idToken: { jwtToken },
|
||||
} = token;
|
||||
verifyToken(jwtToken);
|
||||
return () => {
|
||||
if (timer) timer.terminate();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [token])
|
||||
}, [token]);
|
||||
|
||||
const verifyToken = async (accessToken) => {
|
||||
const result = await auth.verify(accessToken);
|
||||
const refreshTokens = async () => {
|
||||
if (!token || !token.refreshToken || !token.refreshToken.token) return null;
|
||||
const result = await refresh(token.refreshToken.token);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!result.authenticated || !token.idToken.payload || !token.idToken.payload.exp) {
|
||||
signOut();
|
||||
return;
|
||||
}
|
||||
const isError = (resp) => {
|
||||
if (resp === null) return true;
|
||||
if (resp && resp.error) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const duration = (1000 * token.idToken.payload.exp) - (new Date()).getTime();
|
||||
const startSessionTimer = () => {
|
||||
const duration = 1000 * token.idToken.payload.exp - new Date().getTime();
|
||||
if (!timer) {
|
||||
timer = getTimerWorker();
|
||||
timer.onMessage((e) => {
|
||||
timer = getTimerWorker();
|
||||
timer.onMessage(async (e) => {
|
||||
if (e.data === "timeout") {
|
||||
const t = await refreshTokens();
|
||||
if (!isError(t)) return;
|
||||
signOut();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
timer.start(duration);
|
||||
};
|
||||
|
||||
const signIn = async (username, password) => {
|
||||
const verifyToken = async (idToken) => {
|
||||
try {
|
||||
const result = await auth.verify(idToken);
|
||||
|
||||
if (
|
||||
!result.authenticated ||
|
||||
!token.idToken.payload ||
|
||||
!token.idToken.payload.exp
|
||||
) {
|
||||
const t = await refreshTokens();
|
||||
if (!isError(t)) return;
|
||||
signOut();
|
||||
return;
|
||||
}
|
||||
|
||||
startSessionTimer();
|
||||
}
|
||||
catch (e) {
|
||||
setError(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
const signIn = async (code) => {
|
||||
let result = null;
|
||||
|
||||
try {
|
||||
if (!username) throw new Error('Email is required');
|
||||
if (!password) throw new Error('Password is required');
|
||||
|
||||
if (!code) return;
|
||||
|
||||
setFetching(true);
|
||||
setError(null);
|
||||
|
||||
result = await auth.signIn(username, password);
|
||||
|
||||
if (result.message) throw new Error(result.message);
|
||||
result = await auth.signIn(code);
|
||||
if (result.message) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
|
||||
signedIn(result);
|
||||
}
|
||||
catch (error) {
|
||||
setError(error.message);
|
||||
}
|
||||
finally {
|
||||
setFetching(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const signUp = async (username, password, confirmPassword) => {
|
||||
let result = null;
|
||||
|
||||
try {
|
||||
if (!username) throw new Error('Email is required');
|
||||
if (!password) throw new Error('Password is required');
|
||||
if (password !== confirmPassword) throw new Error('Passwords do not match');
|
||||
|
||||
setFetching(true);
|
||||
setError(null);
|
||||
|
||||
result = await auth.signUp(username, password);
|
||||
if (result.message) throw new Error(result.message);
|
||||
}
|
||||
catch (error) {
|
||||
setError(error.message);
|
||||
}
|
||||
finally {
|
||||
setFetching(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const signUpAndIn = async (username, password, confirmPassword) => {
|
||||
let result = null;
|
||||
|
||||
try {
|
||||
result = await signUp(username, password, confirmPassword);
|
||||
if (result.message) throw new Error(result.message);
|
||||
result = await signIn(username, password);
|
||||
}
|
||||
catch (error) {
|
||||
setError(error.message);
|
||||
}
|
||||
finally {
|
||||
} catch (err) {
|
||||
setError(`Sign in error. ${err.message}`);
|
||||
} finally {
|
||||
setFetching(false);
|
||||
}
|
||||
|
||||
@@ -117,27 +105,61 @@ export const UserProvider = ({ children }) => {
|
||||
|
||||
const signOut = () => {
|
||||
setToken(null);
|
||||
if (!localStorage) return;
|
||||
localStorage.removeItem("token");
|
||||
if (localStorage) {
|
||||
localStorage.removeItem("token");
|
||||
}
|
||||
return getLogoutURL();
|
||||
};
|
||||
|
||||
const signedIn = (token) => {
|
||||
setToken(token);
|
||||
if (!localStorage || !token || !token.idToken) return;
|
||||
localStorage.setItem("token", JSON.stringify(token));
|
||||
}
|
||||
const signedIn = (value) => {
|
||||
setToken(value);
|
||||
if (!localStorage || !value || !value.idToken) return;
|
||||
localStorage.setItem("token", JSON.stringify(value));
|
||||
};
|
||||
|
||||
const refresh = async (value) => {
|
||||
let result = null;
|
||||
|
||||
try {
|
||||
if (!value) {
|
||||
throw new Error("Token required");
|
||||
}
|
||||
setFetching(true);
|
||||
setError(null);
|
||||
|
||||
// eslint-disable-next-line
|
||||
result = await auth.refresh(value);
|
||||
|
||||
if (result.message) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
signedIn(result);
|
||||
} catch (err) {
|
||||
setError(`Refresh error. ${err.message}`);
|
||||
} finally {
|
||||
setFetching(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const getAuthorizeURL = () => auth.ssoAuthorize();
|
||||
const getLogoutURL = () => auth.ssoLogout();
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={{
|
||||
fetching,
|
||||
token,
|
||||
error,
|
||||
setError,
|
||||
signIn,
|
||||
signUp,
|
||||
signUpAndIn,
|
||||
signOut,
|
||||
}}>
|
||||
<UserContext.Provider
|
||||
value={{
|
||||
fetching,
|
||||
token,
|
||||
error,
|
||||
setError,
|
||||
signIn,
|
||||
signOut,
|
||||
refresh,
|
||||
getAuthorizeURL,
|
||||
getLogoutURL,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</UserContext.Provider>
|
||||
);
|
||||
|
||||
@@ -1,98 +1,88 @@
|
||||
jest.mock("../../services/auth");
|
||||
jest.mock("../../services/timer");
|
||||
|
||||
import {render, cleanup, screen, fireEvent, waitFor} from "@testing-library/react"
|
||||
import { UserProvider, useUserContext } from "../Contexts/UserContext";
|
||||
import {
|
||||
render,
|
||||
cleanup,
|
||||
screen,
|
||||
fireEvent,
|
||||
waitFor,
|
||||
} from "@testing-library/react";
|
||||
import { UserProvider, useUserContext } from "../Contexts/UserContext";
|
||||
import auth from "../../services/auth";
|
||||
import getTimerWorker from "../../services/timer";
|
||||
|
||||
const TEST_TOKEN = { idToken: {
|
||||
jwtToken: "TEST",
|
||||
payload: {
|
||||
exp: (new Date().getTime() / 1000)
|
||||
const TEST_TOKEN = {
|
||||
idToken: {
|
||||
jwtToken: "TEST",
|
||||
payload: {
|
||||
exp: new Date().getTime() / 1000,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const INVALID_TOKEN_RESPONSE = {
|
||||
error: "Bad Request Error",
|
||||
message: "Bad Request Message",
|
||||
};
|
||||
|
||||
const setupRefreshEnv = (refreshResponse, authenticated) => {
|
||||
auth.setRefreshResponse(refreshResponse);
|
||||
auth.setVerifyResponse({ authenticated });
|
||||
};
|
||||
|
||||
const setupSignInEnv = (refreshResponse, authenticated) => {
|
||||
auth.setSignInResponse(refreshResponse);
|
||||
auth.setVerifyResponse({ authenticated });
|
||||
};
|
||||
|
||||
const checkBaseResults = (error, fetching, token) => {
|
||||
expect(screen.getByTestId("error").innerHTML).toEqual(error);
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual(fetching);
|
||||
expect(screen.getByTestId("token").innerHTML).toEqual(token);
|
||||
};
|
||||
|
||||
const checkTokenResults = (timer, token) => {
|
||||
expect(timer.start.mock.calls.length).toEqual(1);
|
||||
expect(timer.onMessage.mock.calls.length).toEqual(1);
|
||||
expect(timer.stop.mock.calls.length).toEqual(0);
|
||||
expect(timer.terminate.mock.calls.length).toEqual(0);
|
||||
if (!localStorage) {
|
||||
expect(localStorage.getItem("token")).toEqual(token);
|
||||
localStorage.removeItem("token");
|
||||
}
|
||||
}};
|
||||
};
|
||||
|
||||
describe("UseContext", () => {
|
||||
|
||||
describe("Signup", () => {
|
||||
beforeEach(() => {
|
||||
const TestComp = () => {
|
||||
const { signUp, error, fetching } = useUserContext();
|
||||
return (
|
||||
<>
|
||||
<div data-testid="error">{error}</div>
|
||||
<div data-testid="fetching">{fetching.toString()}</div>
|
||||
<button data-testid="signUpNoEmail" onClick={() => signUp("")}/>
|
||||
<button data-testid="signUpNoPassword" onClick={() => signUp("test@test.com", "")}/>
|
||||
<button data-testid="signUpBadConfirm" onClick={() => signUp("test@test.com", "password", "")}/>
|
||||
<button data-testid="signUp" onClick={() => signUp("test@test.com", "password", "password")}/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
render(<UserProvider><TestComp /></UserProvider>);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it("Initial state", () => {
|
||||
expect(screen.getByTestId("error").innerHTML).toEqual("");
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||
});
|
||||
|
||||
it("Error with no email address", () => {
|
||||
fireEvent.click(screen.getByTestId("signUpNoEmail"));
|
||||
expect(screen.getByTestId("error").innerHTML).toEqual("Email is required");
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||
});
|
||||
|
||||
it("Error with no password", () => {
|
||||
fireEvent.click(screen.getByTestId("signUpNoPassword"));
|
||||
expect(screen.getByTestId("error").innerHTML).toEqual("Password is required");
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||
});
|
||||
|
||||
it("Error with non-matching password", () => {
|
||||
fireEvent.click(screen.getByTestId("signUpBadConfirm"));
|
||||
expect(screen.getByTestId("error").innerHTML).toEqual("Passwords do not match");
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||
});
|
||||
|
||||
it("No error sign up", async () => {
|
||||
fireEvent.click(screen.getByTestId("signUp"));
|
||||
await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
|
||||
expect(screen.getByTestId("error").innerHTML).toEqual("");
|
||||
});
|
||||
|
||||
it("Handle server error", async () => {
|
||||
auth.setSignInResponse({ message: "SIGN-IN-ERROR" })
|
||||
auth.setSignUpResponse({ message: "SIGN-UP-ERROR", error: "ERR" });
|
||||
fireEvent.click(screen.getByTestId("signUp"));
|
||||
await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
|
||||
expect(screen.getByTestId("error").innerHTML).toEqual("SIGN-UP-ERROR");
|
||||
auth.setSignUpResponse({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Signin", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
const TestComp = () => {
|
||||
const { signIn, error, token, fetching } = useUserContext();
|
||||
const {
|
||||
signIn,
|
||||
error,
|
||||
token,
|
||||
fetching,
|
||||
} = useUserContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-testid="error">{error}</div>
|
||||
<div data-testid="fetching">{fetching.toString()}</div>
|
||||
<div data-testid="token">{JSON.stringify(token)}</div>
|
||||
<button data-testid="signInNoEmail" onClick={() => signIn("")}/>
|
||||
<button data-testid="signInNoPassword" onClick={() => signIn("test@test.com", "")}/>
|
||||
<button data-testid="signIn" onClick={() => signIn("test@test.com", "password", "password")}/>
|
||||
<button data-testid="signInNoCode" onClick={() => signIn("")} />
|
||||
<button
|
||||
data-testid="signInInvalidCode"
|
||||
onClick={() => signIn("INVALID_CODE")}
|
||||
/>
|
||||
<button data-testid="signIn" onClick={() => signIn("TEST_CODE")} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
render(<UserProvider><TestComp /></UserProvider>);
|
||||
render(
|
||||
<UserProvider>
|
||||
<TestComp />
|
||||
</UserProvider>
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -100,51 +90,41 @@ describe("UseContext", () => {
|
||||
});
|
||||
|
||||
it("Initial state", () => {
|
||||
expect(screen.getByTestId("error").innerHTML).toEqual("");
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||
expect(screen.getByTestId("token").innerHTML).toEqual("null");
|
||||
checkBaseResults("", "false", "null");
|
||||
});
|
||||
|
||||
it("Error with no email address", () => {
|
||||
fireEvent.click(screen.getByTestId("signInNoEmail"));
|
||||
expect(screen.getByTestId("error").innerHTML).toEqual("Email is required");
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||
expect(screen.getByTestId("token").innerHTML).toEqual("null");
|
||||
it("No auth code", () => {
|
||||
fireEvent.click(screen.getByTestId("signInNoCode"));
|
||||
|
||||
checkBaseResults("", "false", "null");
|
||||
});
|
||||
|
||||
it("Error with no password", () => {
|
||||
fireEvent.click(screen.getByTestId("signInNoPassword"));
|
||||
expect(screen.getByTestId("error").innerHTML).toEqual("Password is required");
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||
expect(screen.getByTestId("token").innerHTML).toEqual("null");
|
||||
it("Invalid auth code", async () => {
|
||||
setupSignInEnv(INVALID_TOKEN_RESPONSE, false);
|
||||
|
||||
fireEvent.click(screen.getByTestId("signInInvalidCode"));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
|
||||
);
|
||||
|
||||
checkBaseResults("Sign in error. Bad Request Message", "false", "null");
|
||||
});
|
||||
|
||||
it("No error sign in", async () => {
|
||||
it("Sign in form", async () => {
|
||||
const TOKEN_STRING = JSON.stringify(TEST_TOKEN);
|
||||
const timer = getTimerWorker();
|
||||
|
||||
auth.setSignInResponse(TEST_TOKEN);
|
||||
auth.setVerifyResponse({ authenticated: true })
|
||||
fireEvent.click(screen.getByTestId("signIn"));
|
||||
await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
|
||||
expect(screen.getByTestId("error").innerHTML).toEqual("");
|
||||
expect(screen.getByTestId("token").innerHTML).toEqual(TOKEN_STRING);
|
||||
expect(timer.start.mock.calls.length).toEqual(1);
|
||||
expect(timer.onMessage.mock.calls.length).toEqual(1);
|
||||
expect(timer.stop.mock.calls.length).toEqual(0);
|
||||
expect(timer.terminate.mock.calls.length).toEqual(0);
|
||||
if (!localStorage) {
|
||||
expect(localStorage.getItem("token")).toEqual(TOKEN_STRING);
|
||||
localStorage.removeItem("token");
|
||||
}
|
||||
});
|
||||
setupSignInEnv(TEST_TOKEN, true);
|
||||
|
||||
it("Handle server error", async () => {
|
||||
auth.setSignInResponse({ message: "SERVER-ERROR", error: "ERR" });
|
||||
fireEvent.click(screen.getByTestId("signIn"));
|
||||
await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
|
||||
expect(screen.getByTestId("error").innerHTML).toEqual("SERVER-ERROR");
|
||||
auth.setSignUpResponse({});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
|
||||
);
|
||||
|
||||
checkBaseResults("", "false", TOKEN_STRING);
|
||||
checkTokenResults(timer, TOKEN_STRING);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -157,15 +137,21 @@ describe("UseContext", () => {
|
||||
<div data-testid="error">{error}</div>
|
||||
<div data-testid="fetching">{fetching.toString()}</div>
|
||||
<div data-testid="token">{JSON.stringify(token)}</div>
|
||||
<button data-testid="signIn" onClick={() => signIn("test@test.com", "password", "password")}/>
|
||||
<button data-testid="signOut" onClick={() => signOut()}/>
|
||||
<button data-testid="signIn" onClick={() => signIn("TEST_CODE")} />
|
||||
<button data-testid="signOut" onClick={() => signOut()} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
render(<UserProvider><TestComp /></UserProvider>);
|
||||
render(
|
||||
<UserProvider>
|
||||
<TestComp />
|
||||
</UserProvider>
|
||||
);
|
||||
auth.setSignInResponse(TEST_TOKEN);
|
||||
fireEvent.click(screen.getByTestId("signIn"));
|
||||
await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -175,11 +161,81 @@ describe("UseContext", () => {
|
||||
|
||||
it("Token cleared", () => {
|
||||
fireEvent.click(screen.getByTestId("signOut"));
|
||||
expect(screen.getByTestId("error").innerHTML).toEqual("");
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
|
||||
expect(screen.getByTestId("token").innerHTML).toEqual("null");
|
||||
|
||||
checkBaseResults("", "false", "null");
|
||||
if (!localStorage) return;
|
||||
expect(localStorage.getItem('token')).toBeNull();
|
||||
})
|
||||
})
|
||||
});
|
||||
expect(localStorage.getItem("token")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Refresh", () => {
|
||||
beforeEach(() => {
|
||||
const TestComp = () => {
|
||||
const {
|
||||
refresh,
|
||||
error,
|
||||
token,
|
||||
fetching,
|
||||
} = useUserContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-testid="error">{error}</div>
|
||||
<div data-testid="fetching">{fetching.toString()}</div>
|
||||
<div data-testid="token">{JSON.stringify(token)}</div>
|
||||
<button data-testid="refreshNoToken" onClick={() => refresh("")} />
|
||||
<button
|
||||
data-testid="refreshInvalidToken"
|
||||
onClick={() => refresh("INVALID_TOKEN")}
|
||||
/>
|
||||
<button data-testid="refreshValidToken" onClick={() => refresh("TEST_TOKEN")} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
render(
|
||||
<UserProvider>
|
||||
<TestComp />
|
||||
</UserProvider>
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it("Initial state", () => {
|
||||
checkBaseResults("", "false", "null");
|
||||
});
|
||||
|
||||
it("No refresh token", () => {
|
||||
fireEvent.click(screen.getByTestId("refreshNoToken"));
|
||||
checkBaseResults("Refresh error. Token required", "false", "null");
|
||||
});
|
||||
|
||||
it("Invalid refresh token", async () => {
|
||||
setupRefreshEnv(INVALID_TOKEN_RESPONSE, false);
|
||||
|
||||
fireEvent.click(screen.getByTestId("refreshInvalidToken"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
|
||||
);
|
||||
|
||||
checkBaseResults("Refresh error. Bad Request Message", "false", "null");
|
||||
});
|
||||
|
||||
it("Valid refresh token", async () => {
|
||||
const TOKEN_STRING = JSON.stringify(TEST_TOKEN);
|
||||
const timer = getTimerWorker();
|
||||
|
||||
setupRefreshEnv(TEST_TOKEN, true);
|
||||
|
||||
fireEvent.click(screen.getByTestId("refreshValidToken"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
|
||||
);
|
||||
|
||||
checkBaseResults("", "false", TOKEN_STRING);
|
||||
checkTokenResults(timer, TOKEN_STRING);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,11 +5,7 @@ let progress = 0;
|
||||
let status = null;
|
||||
|
||||
export const FileUploadProvider = ({ children }) => {
|
||||
return (
|
||||
<div data-testid="mocked-fileuploadprovider">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
return <div data-testid="mocked-fileuploadprovider">{children}</div>;
|
||||
};
|
||||
|
||||
export const useFileUploadContext = () => ({
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
let token = null;
|
||||
let fetching = false;
|
||||
let error = null;
|
||||
let signInResp = {};
|
||||
let signUpResp = {};
|
||||
let authorizeURL = "https://cognito.com/authorize?redirect=https://example.com/callback";
|
||||
let logoutURL = "https://cognito.com/logout?redirect=https://example.com/callback";
|
||||
|
||||
export const UserProvider = ({ children }) => {
|
||||
return (
|
||||
<div data-testid="mocked-userprovider">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
return <div data-testid="mocked-userprovider">{children}</div>;
|
||||
};
|
||||
|
||||
export const useUserContext = () => ({
|
||||
token,
|
||||
fetching,
|
||||
error,
|
||||
signIn: jest.fn(() => signInResp),
|
||||
signOut: jest.fn(),
|
||||
getAuthorizeURL: jest.fn(() => authorizeURL),
|
||||
getLogoutURL: jest.fn(() => logoutURL),
|
||||
setError: jest.fn((value) => {
|
||||
error = value;
|
||||
}),
|
||||
signIn: jest.fn(() => signInResp),
|
||||
signUp: jest.fn(() => signUpResp),
|
||||
signOut: jest.fn(),
|
||||
signUpAndIn: jest.fn(),
|
||||
});
|
||||
|
||||
export const setToken = (val) => {
|
||||
@@ -38,7 +35,3 @@ export const setFetching = (val) => {
|
||||
export const setError = (val) => {
|
||||
error = val;
|
||||
};
|
||||
|
||||
export const setSignUpResp = (val) => {
|
||||
signUpResp = val;
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Typography } from "@material-ui/core";
|
||||
|
||||
export default class ErrorBoundary extends Component {
|
||||
state = {
|
||||
error: '',
|
||||
errorInfo: '',
|
||||
error: "",
|
||||
errorInfo: "",
|
||||
hasError: false,
|
||||
};
|
||||
static getDerivedStateFromError(error) {
|
||||
@@ -15,10 +15,15 @@ export default class ErrorBoundary extends Component {
|
||||
this.setState({ errorInfo });
|
||||
}
|
||||
render() {
|
||||
if (this.state.hasError) return (<Typography variant="h3" align="center" >Oops. An React JS Error Occured.</Typography>);
|
||||
return this.props.children;
|
||||
if (this.state.hasError)
|
||||
return (
|
||||
<Typography variant="h3" align="center">
|
||||
Oops. An React JS Error Occured.
|
||||
</Typography>
|
||||
);
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
ErrorBoundary.propTypes = {
|
||||
children: PropTypes.oneOfType([ PropTypes.object, PropTypes.array ]).isRequired,
|
||||
};
|
||||
children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
|
||||
};
|
||||
|
||||
@@ -1,24 +1,39 @@
|
||||
import React from "react";
|
||||
import { Button, Container, CssBaseline, Grid, Typography } from "@material-ui/core";
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
CssBaseline,
|
||||
Grid,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { DropzoneAreaBase } from "material-ui-dropzone";
|
||||
import { useUserContext } from "../Contexts/UserContext";
|
||||
import { useFileUploadContext, FileUploadProvider } from "../Contexts/FileUploadContext";
|
||||
import { useUserContext } from "../Contexts/UserContext";
|
||||
import {
|
||||
useFileUploadContext,
|
||||
FileUploadProvider,
|
||||
} from "../Contexts/FileUploadContext";
|
||||
import ModalProgressBar from "../ModalProgressBar";
|
||||
import useStyles from "../Styles";
|
||||
import useStyles from "../useStyles";
|
||||
|
||||
const FileUploadZone = ({ classes, token }) => {
|
||||
const { upload, rejectedFile } = useFileUploadContext();
|
||||
const { token: { idToken: { jwtToken : authToken } } } = useUserContext();
|
||||
const {
|
||||
token: {
|
||||
idToken: { jwtToken: authToken },
|
||||
},
|
||||
} = useUserContext();
|
||||
|
||||
return (
|
||||
<form className={classes.form} noValidate>
|
||||
<DropzoneAreaBase
|
||||
<DropzoneAreaBase
|
||||
id="dropzone"
|
||||
maxFileSize={1e+9}
|
||||
maxFileSize={1e9}
|
||||
filesLimit={1}
|
||||
showAlerts={false}
|
||||
onAdd={(files) => upload(files, authToken)}
|
||||
onDropRejected={(files) => { rejectedFile(files) }}
|
||||
onDropRejected={(files) => {
|
||||
rejectedFile(files);
|
||||
}}
|
||||
/>
|
||||
<ModalProgressBar />
|
||||
</form>
|
||||
@@ -26,25 +41,25 @@ const FileUploadZone = ({ classes, token }) => {
|
||||
};
|
||||
|
||||
export default function FileUploadForm() {
|
||||
const { signOut } = useUserContext();
|
||||
const classes = useStyles();
|
||||
const { signOut } = useUserContext();
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Container component="main" maxWidth="xs">
|
||||
<CssBaseline />
|
||||
<div className={classes.paper}>
|
||||
<Typography component="h1" variant="h5">
|
||||
Upload file
|
||||
</Typography>
|
||||
<FileUploadProvider>
|
||||
<FileUploadZone classes={classes} />
|
||||
</FileUploadProvider>
|
||||
<Grid container>
|
||||
<Grid item >
|
||||
<Button onClick={signOut}>Sign Out</Button>
|
||||
</Grid>
|
||||
return (
|
||||
<Container component="main" maxWidth="xs">
|
||||
<CssBaseline />
|
||||
<div className={classes.paper}>
|
||||
<Typography component="h1" variant="h5">
|
||||
Upload file
|
||||
</Typography>
|
||||
<FileUploadProvider>
|
||||
<FileUploadZone classes={classes} />
|
||||
</FileUploadProvider>
|
||||
<Grid container>
|
||||
<Grid item>
|
||||
<Button onClick={signOut}>Sign Out</Button>
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
import { Snackbar } from "@material-ui/core";
|
||||
import { useUserContext } from './Contexts/UserContext';
|
||||
import { useUserContext } from "./Contexts/UserContext";
|
||||
|
||||
export const MessageBar = () => {
|
||||
const { error, setError } = useUserContext();
|
||||
const open = (error !== null);
|
||||
const open = error !== null;
|
||||
|
||||
return (<Snackbar
|
||||
open={open}
|
||||
message={error}
|
||||
anchorOrigin={{ vertical: "top", horizontal: "center" }}
|
||||
autoHideDuration={10000}
|
||||
onClose={() => setError(null)}/>)
|
||||
}
|
||||
return (
|
||||
<Snackbar
|
||||
open={open}
|
||||
message={error}
|
||||
anchorOrigin={{ vertical: "top", horizontal: "center" }}
|
||||
autoHideDuration={10000}
|
||||
onClose={() => setError(null)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import Modal from '@material-ui/core/Modal';
|
||||
import Modal from "@material-ui/core/Modal";
|
||||
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import { useFileUploadContext } from "../Contexts/FileUploadContext";
|
||||
import { useFileUploadContext } from "../Contexts/FileUploadContext";
|
||||
|
||||
const getModalStyle = () => {
|
||||
const top = 30;
|
||||
@@ -23,7 +23,13 @@ const getModalStyle = () => {
|
||||
};
|
||||
|
||||
const ModalProgressBar = () => {
|
||||
const { uploading, progress, status, linkURL, cancel } = useFileUploadContext();
|
||||
const {
|
||||
uploading,
|
||||
progress,
|
||||
status,
|
||||
linkURL,
|
||||
cancel,
|
||||
} = useFileUploadContext();
|
||||
|
||||
const modalStyle = getModalStyle();
|
||||
const onClickCancel = cancel;
|
||||
@@ -32,15 +38,20 @@ const ModalProgressBar = () => {
|
||||
<Modal open={uploading}>
|
||||
<div style={modalStyle}>
|
||||
{status && <p>{status}</p>}
|
||||
{linkURL && <p><a href={linkURL} target="_blank" rel="noreferrer">View</a></p>}
|
||||
{linkURL && (
|
||||
<p>
|
||||
<a href={linkURL} target="_blank" rel="noreferrer">
|
||||
View
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
<LinearProgress variant="determinate" value={progress} />
|
||||
<Button onClick={onClickCancel}>
|
||||
{ progress === 100 || progress === -1 ? "Done" : "Cancel" }
|
||||
{progress === 100 || progress === -1 ? "Done" : "Cancel"}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
export default ModalProgressBar;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import React from "react";
|
||||
import { Redirect, Route } from "react-router-dom";
|
||||
|
||||
export const TYPES = {
|
||||
PUBLIC: 0,
|
||||
@@ -9,10 +9,9 @@ export const TYPES = {
|
||||
|
||||
export const AuthRoute = ({ token, type, ...others }) => {
|
||||
if (!token && type === TYPES.PROTECTED) {
|
||||
return <Redirect to="/" />;
|
||||
}
|
||||
else if (token && type === TYPES.GUEST) {
|
||||
return <Redirect to="/" />;
|
||||
} else if (token && type === TYPES.GUEST) {
|
||||
return <Redirect to="/home" />;
|
||||
}
|
||||
return <Route render {...others} />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import { useUserContext } from '../Contexts/UserContext';
|
||||
import React from "react";
|
||||
import { Redirect, Route } from "react-router-dom";
|
||||
import { useUserContext } from "../Contexts/UserContext";
|
||||
|
||||
export const ProtectedRoute = ({ render, ...others }) => {
|
||||
const context = useUserContext();
|
||||
const { token, setError } = context;
|
||||
if (!token) {
|
||||
setError('Please sign in to access');
|
||||
return <Redirect to="/" />;
|
||||
}
|
||||
return <Route render {...others} />;
|
||||
}
|
||||
const context = useUserContext();
|
||||
const { token, setError } = context;
|
||||
if (!token) {
|
||||
setError("Please sign in to access");
|
||||
return <Redirect to="/" />;
|
||||
}
|
||||
return <Route render {...others} />;
|
||||
};
|
||||
|
||||
@@ -1,34 +1,39 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import {
|
||||
BrowserRouter,
|
||||
Switch,
|
||||
} from 'react-router-dom';
|
||||
import React, { Suspense } from "react";
|
||||
import { BrowserRouter, Switch } from "react-router-dom";
|
||||
|
||||
import { AuthRoute, TYPES } from '../Routes/AuthRoute'
|
||||
import { MessageBar } from '../MessageBar';
|
||||
import { useUserContext } from '../Contexts/UserContext';
|
||||
import { AuthRoute, TYPES } from "../Routes/AuthRoute";
|
||||
import { MessageBar } from "../MessageBar";
|
||||
import { useUserContext } from "../Contexts/UserContext";
|
||||
|
||||
const SignInForm = React.lazy(() => import('../SignInForm'));
|
||||
const SignUpForm = React.lazy(() => import('../SignUpForm'));
|
||||
const FileUploadForm = React.lazy(() => import('../FileUploadForm'));
|
||||
const PageNotFound = React.lazy(() => import('../404'));
|
||||
const SSOForm = React.lazy(() => import("../SSOForm"));
|
||||
const FileUploadForm = React.lazy(() => import("../FileUploadForm"));
|
||||
const PageNotFound = React.lazy(() => import("../404"));
|
||||
|
||||
const SiteRoutes = () => {
|
||||
const { token } = useUserContext();
|
||||
return (
|
||||
<Suspense fallback={"Loading..."}>
|
||||
<Suspense fallback={"Loading..."}>
|
||||
<MessageBar />
|
||||
<BrowserRouter>
|
||||
<Switch>
|
||||
<AuthRoute path="/" exact render={() => <SignInForm />} type={TYPES.GUEST} token={token} />
|
||||
<AuthRoute path="/signup" exact render={() => <SignUpForm />} type={TYPES.GUEST} token={token} />
|
||||
<AuthRoute path="/home" render={() => <FileUploadForm />} type={TYPES.PROTECTED} token={token} />
|
||||
<AuthRoute
|
||||
path="/"
|
||||
exact
|
||||
render={() => <SSOForm />}
|
||||
type={TYPES.GUEST}
|
||||
token={token}
|
||||
/>
|
||||
<AuthRoute
|
||||
path="/home"
|
||||
render={() => <FileUploadForm />}
|
||||
type={TYPES.PROTECTED}
|
||||
token={token}
|
||||
/>
|
||||
<PageNotFound />
|
||||
</Switch>
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default SiteRoutes;
|
||||
export default SiteRoutes;
|
||||
|
||||
19
src/components/SSOForm/SSOForm.test.js
Normal file
19
src/components/SSOForm/SSOForm.test.js
Normal file
@@ -0,0 +1,19 @@
|
||||
jest.mock("../Contexts/UserContext");
|
||||
|
||||
import React from "react";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { render, cleanup } from "@testing-library/react";
|
||||
import SSOForm from "./index";
|
||||
|
||||
describe("Sign In Form", () => {
|
||||
it("Should render", () => {
|
||||
const { container } = render(
|
||||
<BrowserRouter>
|
||||
<SSOForm />
|
||||
</BrowserRouter>
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
cleanup();
|
||||
});
|
||||
});
|
||||
40
src/components/SSOForm/__snapshots__/SSOForm.test.js.snap
Normal file
40
src/components/SSOForm/__snapshots__/SSOForm.test.js.snap
Normal file
@@ -0,0 +1,40 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Sign In Form Should render 1`] = `
|
||||
<div>
|
||||
<main
|
||||
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-paper-1"
|
||||
>
|
||||
<h1
|
||||
class="MuiTypography-root MuiTypography-h5"
|
||||
>
|
||||
Fisker OTA Portal
|
||||
</h1>
|
||||
<form
|
||||
action="{onSubmit}"
|
||||
class="makeStyles-form-3"
|
||||
novalidate=""
|
||||
>
|
||||
<a
|
||||
aria-disabled="false"
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-4 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||
href="https://cognito.com/authorize?redirect=https://example.com/callback"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label"
|
||||
>
|
||||
Sign In
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
`;
|
||||
45
src/components/SSOForm/index.jsx
Normal file
45
src/components/SSOForm/index.jsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { Button, Container, CssBaseline, Typography } from "@material-ui/core";
|
||||
import { useUserContext } from "../Contexts/UserContext";
|
||||
import useStyles from "../useStyles";
|
||||
|
||||
const getCode = (search) => {
|
||||
if (!search) return null;
|
||||
const s = new URLSearchParams(search);
|
||||
return s.get("code");
|
||||
};
|
||||
|
||||
export default function SignInForm() {
|
||||
const classes = useStyles();
|
||||
const { getAuthorizeURL, signIn } = useUserContext();
|
||||
|
||||
useEffect(() => {
|
||||
const code = getCode(document.location.search);
|
||||
if (!code) return;
|
||||
signIn(code);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Container component="main" maxWidth="xs">
|
||||
<CssBaseline />
|
||||
<div className={classes.paper}>
|
||||
<Typography component="h1" variant="h5">
|
||||
Fisker OTA Portal
|
||||
</Typography>
|
||||
<form className={classes.form} noValidate action="{onSubmit}">
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.submit}
|
||||
href={getAuthorizeURL()}
|
||||
>
|
||||
Sign In
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
jest.mock("../Contexts/UserContext");
|
||||
|
||||
import React from "react";
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { render, cleanup } from "@testing-library/react"
|
||||
import SignInForm from './index';
|
||||
|
||||
describe("Sign In Form", () => {
|
||||
|
||||
it("Should render", () => {
|
||||
const { container } = render(<BrowserRouter><SignInForm /></BrowserRouter>);
|
||||
expect(container).toMatchSnapshot();
|
||||
cleanup();
|
||||
})
|
||||
})
|
||||
@@ -1,145 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Sign In Form Should render 1`] = `
|
||||
<div>
|
||||
<main
|
||||
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-paper-1"
|
||||
>
|
||||
<h1
|
||||
class="MuiTypography-root MuiTypography-h5"
|
||||
>
|
||||
Sign in
|
||||
</h1>
|
||||
<form
|
||||
action="{onSubmit}"
|
||||
class="makeStyles-form-3"
|
||||
novalidate=""
|
||||
>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused Mui-required Mui-required"
|
||||
data-shrink="true"
|
||||
for="email"
|
||||
id="email-label"
|
||||
>
|
||||
Email Address
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||
>
|
||||
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
autocomplete="email"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||
id="email"
|
||||
name="email"
|
||||
required=""
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-7 PrivateNotchedOutline-legendNotched-8"
|
||||
>
|
||||
<span>
|
||||
Email Address
|
||||
*
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||
data-shrink="false"
|
||||
for="password"
|
||||
id="password-label"
|
||||
>
|
||||
Password
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||
>
|
||||
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
autocomplete="current-password"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||
id="password"
|
||||
name="password"
|
||||
required=""
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-7"
|
||||
>
|
||||
<span>
|
||||
Password
|
||||
*
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-4 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||
tabindex="0"
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label"
|
||||
>
|
||||
Sign In
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-container"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-body2 MuiTypography-colorPrimary"
|
||||
href="/signup"
|
||||
>
|
||||
Don't have an account? Sign Up
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,78 +0,0 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { Button, Container, CssBaseline, Grid, Link, TextField, Typography } from '@material-ui/core';
|
||||
import { useUserContext } from '../Contexts/UserContext';
|
||||
import useStyles from '../Styles';
|
||||
|
||||
export default function SignInForm() {
|
||||
const classes = useStyles();
|
||||
const emailEl = useRef(null);
|
||||
const passwordEl = useRef(null);
|
||||
const { fetching, signIn, setError } = useUserContext();
|
||||
const onSubmit = async (event) => {
|
||||
try {
|
||||
event.preventDefault();
|
||||
const username = emailEl.current.value;
|
||||
const password = passwordEl.current.value;
|
||||
await signIn(username, password);
|
||||
}
|
||||
catch (e) {
|
||||
setError(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container component="main" maxWidth="xs">
|
||||
<CssBaseline />
|
||||
<div className={classes.paper}>
|
||||
<Typography component="h1" variant="h5">
|
||||
Sign in
|
||||
</Typography>
|
||||
<form className={classes.form} noValidate action="{onSubmit}">
|
||||
<TextField
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
id="email"
|
||||
label="Email Address"
|
||||
name="email"
|
||||
autoComplete="email"
|
||||
autoFocus
|
||||
inputRef={emailEl}
|
||||
/>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
name="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
id="password"
|
||||
autoComplete="current-password"
|
||||
inputRef={passwordEl}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={fetching}
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.submit}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
{ fetching ? "Signing In..." : "Sign In" }
|
||||
</Button>
|
||||
<Grid container>
|
||||
<Grid item>
|
||||
<Link component={RouterLink} to="/signup" variant="body2">
|
||||
{"Don't have an account? Sign Up"}
|
||||
</Link>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
jest.mock("../Contexts/UserContext");
|
||||
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { render, cleanup, screen, fireEvent, waitFor } from "@testing-library/react";
|
||||
import SignUpForm from './index';
|
||||
|
||||
describe.only("Sign Up Form", () => {
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it("Should render", () => {
|
||||
const { container } = render(<BrowserRouter><SignUpForm /></BrowserRouter>);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
})
|
||||
@@ -1,189 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Sign Up Form Should render 1`] = `
|
||||
<div>
|
||||
<main
|
||||
class="MuiContainer-root MuiContainer-maxWidthXs"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-paper-1"
|
||||
>
|
||||
<h1
|
||||
class="MuiTypography-root MuiTypography-h5"
|
||||
>
|
||||
Sign up
|
||||
</h1>
|
||||
<form
|
||||
class="makeStyles-form-3"
|
||||
novalidate=""
|
||||
>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused Mui-required Mui-required"
|
||||
data-shrink="true"
|
||||
for="email"
|
||||
id="email-label"
|
||||
>
|
||||
Email Address
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||
>
|
||||
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
autocomplete="email"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||
id="email"
|
||||
name="email"
|
||||
required=""
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-7 PrivateNotchedOutline-legendNotched-8"
|
||||
>
|
||||
<span>
|
||||
Email Address
|
||||
*
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||
data-shrink="false"
|
||||
for="password"
|
||||
id="password-label"
|
||||
>
|
||||
Password
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||
>
|
||||
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
autocomplete="new-password"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||
id="password"
|
||||
name="password"
|
||||
required=""
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-7"
|
||||
>
|
||||
<span>
|
||||
Password
|
||||
*
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||
data-shrink="false"
|
||||
for="passwordConfirm"
|
||||
id="passwordConfirm-label"
|
||||
>
|
||||
Confirm Password
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||
>
|
||||
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||
id="passwordConfirm"
|
||||
name="password"
|
||||
required=""
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-7"
|
||||
>
|
||||
<span>
|
||||
Confirm Password
|
||||
*
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-4 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||
tabindex="0"
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label"
|
||||
>
|
||||
Sign Up
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-container"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item"
|
||||
>
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-body2 MuiTypography-colorPrimary"
|
||||
href="/"
|
||||
>
|
||||
Already have an account? Sign In
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,90 +0,0 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { Button, Container, CssBaseline, Grid, Link, TextField, Typography } from '@material-ui/core';
|
||||
import useStyles from '../Styles';
|
||||
import { useUserContext } from '../Contexts/UserContext';
|
||||
|
||||
export default function SignInForm() {
|
||||
const { signUpAndIn, fetching, setError } = useUserContext();
|
||||
const classes = useStyles();
|
||||
const emailEl = useRef(null);
|
||||
const passwordEl = useRef(null);
|
||||
const confirmEl = useRef(null);
|
||||
const onSubmit = async (event) => {
|
||||
try {
|
||||
event.preventDefault();
|
||||
const email = emailEl.current.value;
|
||||
const password = passwordEl.current.value;
|
||||
const confirm = confirmEl.current.value;
|
||||
await signUpAndIn(email, password, confirm);
|
||||
}
|
||||
catch (e) {
|
||||
setError(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container component="main" maxWidth="xs">
|
||||
<CssBaseline />
|
||||
<div className={classes.paper}>
|
||||
<Typography component="h1" variant="h5">
|
||||
Sign up
|
||||
</Typography>
|
||||
<form className={classes.form} noValidate onSubmit={onSubmit}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
id="email"
|
||||
label="Email Address"
|
||||
name="email"
|
||||
autoComplete="email"
|
||||
autoFocus
|
||||
inputRef={emailEl}
|
||||
/>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
name="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
id="password"
|
||||
autoComplete="new-password"
|
||||
inputRef={passwordEl}
|
||||
/>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
name="password"
|
||||
label="Confirm Password"
|
||||
type="password"
|
||||
id="passwordConfirm"
|
||||
inputRef={confirmEl}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.submit}
|
||||
disabled={fetching}
|
||||
>
|
||||
{ fetching ? "Signing Up..." : "Sign Up" }
|
||||
</Button>
|
||||
<Grid container>
|
||||
<Grid item>
|
||||
<Link component={RouterLink} to="/" variant="body2">
|
||||
{"Already have an account? Sign In"}
|
||||
</Link>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
paper: {
|
||||
marginTop: theme.spacing(8),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
},
|
||||
avatar: {
|
||||
margin: theme.spacing(1),
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
form: {
|
||||
width: '100%', // Fix IE 11 issue.
|
||||
width: "100%", // Fix IE 11 issue.
|
||||
marginTop: theme.spacing(1),
|
||||
},
|
||||
submit: {
|
||||
@@ -20,4 +20,4 @@ const useStyles = makeStyles((theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
export default useStyles;
|
||||
Reference in New Issue
Block a user