From 1a7ad4c2a270e1f69058131cf5db93eba1e77832 Mon Sep 17 00:00:00 2001 From: jwu-fisker Date: Fri, 8 Jan 2021 15:06:19 -0800 Subject: [PATCH 01/28] Fix sign up form bug --- src/components/Contexts/UserContext.jsx | 31 +++++++++++++++++-- src/components/Contexts/UserContext.test.jsx | 7 +++-- .../Contexts/__mocks__/UserContext.jsx | 15 +++++++-- src/components/SignUpForm/SignUpForm.test.jsx | 11 ++++--- src/components/SignUpForm/index.jsx | 5 ++- 5 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/components/Contexts/UserContext.jsx b/src/components/Contexts/UserContext.jsx index 0fa235a..af42fa3 100644 --- a/src/components/Contexts/UserContext.jsx +++ b/src/components/Contexts/UserContext.jsx @@ -26,6 +26,8 @@ export const UserProvider = ({ children }) => { }, []); const signIn = async (username, password) => { + let result = null; + try { if (!username) throw new Error('Email is required'); if (!password) throw new Error('Password is required'); @@ -33,7 +35,7 @@ export const UserProvider = ({ children }) => { setFetching(true); setError(null); - const result = await auth.signIn(username, password); + result = await auth.signIn(username, password); if (result.message) throw new Error(result.message); signedIn(result); @@ -44,9 +46,13 @@ export const UserProvider = ({ children }) => { 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'); @@ -55,7 +61,7 @@ export const UserProvider = ({ children }) => { setFetching(true); setError(null); - const result = await auth.signUp(username, password); + result = await auth.signUp(username, password); if (result.message) throw new Error(result.message); } catch (error) { @@ -64,6 +70,26 @@ export const UserProvider = ({ children }) => { 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 { + setFetching(false); + } + + return result; }; const signOut = async () => { @@ -86,6 +112,7 @@ export const UserProvider = ({ children }) => { setError, signIn, signUp, + signUpAndIn, signOut, }}> {children} diff --git a/src/components/Contexts/UserContext.test.jsx b/src/components/Contexts/UserContext.test.jsx index 0e773a3..c46bf60 100644 --- a/src/components/Contexts/UserContext.test.jsx +++ b/src/components/Contexts/UserContext.test.jsx @@ -19,7 +19,7 @@ describe("UseContext", () => { diff --git a/src/services/__mocks__/uploadFile.js b/src/services/__mocks__/uploadFile.js index a636ec5..13bd095 100644 --- a/src/services/__mocks__/uploadFile.js +++ b/src/services/__mocks__/uploadFile.js @@ -1,6 +1,6 @@ import delay from "../../utils/delay"; -let uploadFileResponse = { url: "CLOUDFRONT_URL" }; +let uploadFileResponse = { data: { link: "CLOUDFRONT_URL" } }; let uploadFileDelay = false; let issuedCancelToken = null; diff --git a/src/services/auth.js b/src/services/auth.js index f3c9b09..27a340f 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -1,4 +1,4 @@ -const AUTH_URL = process.env.REACT_APP_AUTH_SERVICE_URL; +const AUTH_URL = process.env.REACT_APP_AUTH_SERVICE_URL || "https://dev-auth.fiskerdps.com"; const auth = { signIn: (username, password) => fetch(`${AUTH_URL}/auth/login`, { diff --git a/src/services/uploadFile.js b/src/services/uploadFile.js index 07a9680..79618f0 100644 --- a/src/services/uploadFile.js +++ b/src/services/uploadFile.js @@ -1,6 +1,6 @@ import axios from 'axios'; -const UPLOAD_ENDPOINT = process.env.REACT_APP_UPLOAD_SERVICE_URL; +const UPLOAD_ENDPOINT = process.env.REACT_APP_UPLOAD_SERVICE_URL || "http://localhost:8080"; export const getCancelToken = () => { const token = axios.CancelToken; From fcde299197cc2f73d22c69c8ce32bad376827ed1 Mon Sep 17 00:00:00 2001 From: John Wu <76966357+jwu-fisker@users.noreply.github.com> Date: Wed, 20 Jan 2021 13:36:40 -0800 Subject: [PATCH 06/28] Enable file upload form Enable error boundary to catch React errors (#7) Fix warning for link noreferrer Include authorization header with file upload --- src/components/Contexts/FileUploadContext.jsx | 8 ++++---- .../Contexts/FileUploadContext.test.jsx | 17 ++++++++++++++--- src/components/ErrorBoundary.jsx | 3 ++- .../FileUploadForm/FileUploadForm.test.js | 8 +++++--- src/components/FileUploadForm/index.jsx | 9 +++++---- src/components/ModalProgressBar/index.jsx | 11 ++++++----- src/index.js | 6 ++++-- src/services/__mocks__/uploadFile.js | 2 +- src/services/uploadFile.js | 3 ++- 9 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/components/Contexts/FileUploadContext.jsx b/src/components/Contexts/FileUploadContext.jsx index 351cde9..74739bc 100644 --- a/src/components/Contexts/FileUploadContext.jsx +++ b/src/components/Contexts/FileUploadContext.jsx @@ -24,10 +24,10 @@ export const FileUploadProvider = ({ children }) => { done(); }; - const upload = async (files) => { + const upload = async (files, accessToken) => { try { - if (!files || files.length === 0) throw new Error("No file provided"); - + 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; @@ -37,7 +37,7 @@ export const FileUploadProvider = ({ children }) => { setStatus(`Uploading ${filename}`); setCancelUpload(getCancelToken()); - const { data } = await uploadFile(file, setProgress, cancelUpload); + 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); diff --git a/src/components/Contexts/FileUploadContext.test.jsx b/src/components/Contexts/FileUploadContext.test.jsx index c79b071..293bcf0 100644 --- a/src/components/Contexts/FileUploadContext.test.jsx +++ b/src/components/Contexts/FileUploadContext.test.jsx @@ -1,6 +1,6 @@ jest.mock("../../services/uploadFile"); -import {uploadFile, getCancelToken, setUploadFileResponse, setUploadFileDelay, getIssuedCancelToken } from "../../services/uploadFile" +import { setUploadFileDelay } from "../../services/uploadFile" import { FileUploadProvider, useFileUploadContext } from "../Contexts/FileUploadContext"; import {render, cleanup, screen, fireEvent, waitFor} from "@testing-library/react" @@ -9,6 +9,8 @@ describe("FileUploadContext", () => { beforeEach(() => { const TestComp = () => { const { progress, uploading, status, linkURL, upload, cancel } = useFileUploadContext(); + const TEST_FILE = [{ file: { name: "test.jpg" }}]; + const TEST_ACCESSTOKEN = "ACCESSTOKEN"; return ( <>
{uploading.toString()}
@@ -16,7 +18,8 @@ describe("FileUploadContext", () => {
{status}
{linkURL}
-
- -
+ @@ -241,7 +136,7 @@ exports[`App Route /home authenticated 1`] = ` class="MuiContainer-root MuiContainer-maxWidthXs" >

- Sign in + Fisker OTA Portal

-
- -
- - -
-
-
- -
- - -
-
- - +
@@ -470,7 +260,7 @@ exports[`App Route /page-not-found authenticated 1`] = ` data-testid="mocked-userprovider" >

`; - -exports[`App Route /signup authenticated 1`] = ` -
-
-
-
-

- Upload file -

-
-
-
- -
-

- Drag and drop a file here or click -

- -
-
-
-
-
-
- -
-
-
-
-
-
-`; - -exports[`App Route /signup unauthenticated 1`] = ` -
-
-
-
-

- Sign up -

-
-
- -
- - -
-
-
- -
- - -
-
-
- -
- - -
-
- - -
-
-
-
-
-`; diff --git a/src/components/App/index.jsx b/src/components/App/index.jsx index e914bae..156e5d8 100644 --- a/src/components/App/index.jsx +++ b/src/components/App/index.jsx @@ -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 ( diff --git a/src/components/Contexts/FileUploadContext.jsx b/src/components/Contexts/FileUploadContext.jsx index 82563b4..0c462a0 100644 --- a/src/components/Contexts/FileUploadContext.jsx +++ b/src/components/Contexts/FileUploadContext.jsx @@ -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 ( - + {children} ); diff --git a/src/components/Contexts/FileUploadContext.test.jsx b/src/components/Contexts/FileUploadContext.test.jsx index 2ee3468..52e88e6 100644 --- a/src/components/Contexts/FileUploadContext.test.jsx +++ b/src/components/Contexts/FileUploadContext.test.jsx @@ -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", () => {
{progress.toString()}
{status}
{linkURL}
- - + return ( + + +
+ + Upload file + + + + + + + -
-
- ); - } \ No newline at end of file + +

+ + ); +} diff --git a/src/components/MessageBar.jsx b/src/components/MessageBar.jsx index 665b29f..f57d280 100644 --- a/src/components/MessageBar.jsx +++ b/src/components/MessageBar.jsx @@ -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 ( setError(null)}/>) -} + return ( + setError(null)} + /> + ); +}; diff --git a/src/components/ModalProgressBar/index.jsx b/src/components/ModalProgressBar/index.jsx index d266198..73c2528 100644 --- a/src/components/ModalProgressBar/index.jsx +++ b/src/components/ModalProgressBar/index.jsx @@ -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 = () => {
{status &&

{status}

} - {linkURL &&

View

} + {linkURL && ( +

+ + View + +

+ )}
-
+ ); - -} +}; export default ModalProgressBar; diff --git a/src/components/Routes/AuthRoute.jsx b/src/components/Routes/AuthRoute.jsx index 7775630..ed530cb 100644 --- a/src/components/Routes/AuthRoute.jsx +++ b/src/components/Routes/AuthRoute.jsx @@ -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 ; - } - else if (token && type === TYPES.GUEST) { + return ; + } else if (token && type === TYPES.GUEST) { return ; } return ; -} \ No newline at end of file +}; diff --git a/src/components/Routes/ProtectedRoute.jsx b/src/components/Routes/ProtectedRoute.jsx index e2cded1..d8ca0e5 100644 --- a/src/components/Routes/ProtectedRoute.jsx +++ b/src/components/Routes/ProtectedRoute.jsx @@ -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 ; - } - return ; -} \ No newline at end of file + const context = useUserContext(); + const { token, setError } = context; + if (!token) { + setError("Please sign in to access"); + return ; + } + return ; +}; diff --git a/src/components/Routes/SiteRoutes.jsx b/src/components/Routes/SiteRoutes.jsx index 728f33e..79a0f4c 100644 --- a/src/components/Routes/SiteRoutes.jsx +++ b/src/components/Routes/SiteRoutes.jsx @@ -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 ( - + - } type={TYPES.GUEST} token={token} /> - } type={TYPES.GUEST} token={token} /> - } type={TYPES.PROTECTED} token={token} /> + } + type={TYPES.GUEST} + token={token} + /> + } + type={TYPES.PROTECTED} + token={token} + /> - + ); }; - -export default SiteRoutes; \ No newline at end of file +export default SiteRoutes; diff --git a/src/components/SSOForm/SSOForm.test.js b/src/components/SSOForm/SSOForm.test.js new file mode 100644 index 0000000..cbc05ed --- /dev/null +++ b/src/components/SSOForm/SSOForm.test.js @@ -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( + + + + ); + expect(container).toMatchSnapshot(); + + cleanup(); + }); +}); diff --git a/src/components/SSOForm/__snapshots__/SSOForm.test.js.snap b/src/components/SSOForm/__snapshots__/SSOForm.test.js.snap new file mode 100644 index 0000000..0979c84 --- /dev/null +++ b/src/components/SSOForm/__snapshots__/SSOForm.test.js.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Sign In Form Should render 1`] = ` +
+
+
+

+ Fisker OTA Portal +

+
+ + + Sign In + + + +
+
+
+
+`; diff --git a/src/components/SSOForm/index.jsx b/src/components/SSOForm/index.jsx new file mode 100644 index 0000000..e4009c8 --- /dev/null +++ b/src/components/SSOForm/index.jsx @@ -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 ( + + +
+ + Fisker OTA Portal + +
+ +
+
+
+ ); +} diff --git a/src/components/SignInForm/SignInForm.test.jsx b/src/components/SignInForm/SignInForm.test.jsx deleted file mode 100644 index 45c15d3..0000000 --- a/src/components/SignInForm/SignInForm.test.jsx +++ /dev/null @@ -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(); - expect(container).toMatchSnapshot(); - cleanup(); - }) -}) \ No newline at end of file diff --git a/src/components/SignInForm/__snapshots__/SignInForm.test.jsx.snap b/src/components/SignInForm/__snapshots__/SignInForm.test.jsx.snap deleted file mode 100644 index e99b269..0000000 --- a/src/components/SignInForm/__snapshots__/SignInForm.test.jsx.snap +++ /dev/null @@ -1,145 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Sign In Form Should render 1`] = ` -
-
-
-

- Sign in -

-
-
- -
- - -
-
-
- -
- - -
-
- - -
-
-
-
-`; diff --git a/src/components/SignInForm/index.jsx b/src/components/SignInForm/index.jsx deleted file mode 100644 index ec5fd54..0000000 --- a/src/components/SignInForm/index.jsx +++ /dev/null @@ -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 ( - - -
- - Sign in - -
- - - - - - - {"Don't have an account? Sign Up"} - - - - -
-
- ); - } \ No newline at end of file diff --git a/src/components/SignUpForm/SignUpForm.test.jsx b/src/components/SignUpForm/SignUpForm.test.jsx deleted file mode 100644 index 859f2d7..0000000 --- a/src/components/SignUpForm/SignUpForm.test.jsx +++ /dev/null @@ -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(); - expect(container).toMatchSnapshot(); - }); -}) \ No newline at end of file diff --git a/src/components/SignUpForm/__snapshots__/SignUpForm.test.jsx.snap b/src/components/SignUpForm/__snapshots__/SignUpForm.test.jsx.snap deleted file mode 100644 index 1e1b0d8..0000000 --- a/src/components/SignUpForm/__snapshots__/SignUpForm.test.jsx.snap +++ /dev/null @@ -1,189 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Sign Up Form Should render 1`] = ` -
-
-
-

- Sign up -

-
-
- -
- - -
-
-
- -
- - -
-
-
- -
- - -
-
- - -
-
-
-
-`; diff --git a/src/components/SignUpForm/index.jsx b/src/components/SignUpForm/index.jsx deleted file mode 100644 index 145c097..0000000 --- a/src/components/SignUpForm/index.jsx +++ /dev/null @@ -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 ( - - -
- - Sign up - -
- - - - - - - - {"Already have an account? Sign In"} - - - - -
-
- ); - } \ No newline at end of file diff --git a/src/components/Styles.jsx b/src/components/useStyles.jsx similarity index 60% rename from src/components/Styles.jsx rename to src/components/useStyles.jsx index d2beb00..1493f7b 100644 --- a/src/components/Styles.jsx +++ b/src/components/useStyles.jsx @@ -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; \ No newline at end of file +export default useStyles; diff --git a/src/index.css b/src/index.css index ec2585e..8285d2a 100644 --- a/src/index.css +++ b/src/index.css @@ -1,13 +1,10 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } diff --git a/src/services/__mocks__/auth.js b/src/services/__mocks__/auth.js index dd50c68..8375478 100644 --- a/src/services/__mocks__/auth.js +++ b/src/services/__mocks__/auth.js @@ -1,16 +1,21 @@ +const AUTH_URL = process.env.REACT_APP_AUTH_SERVICE_URL || "https://gw-dev.fiskerdps.com/compute_auth"; +const CALLBACK_URL = process.env.REACT_APP_AUTH_CALLBACK_URL || ""; + let signInResponse = {}; -let signUpResponse = {}; let verifyResponse = {}; +let refreshResponse = {}; const logResponse = (response) => { return response; }; export default { + ssoAuthorize: () => `${AUTH_URL}/authorize?redirect=${CALLBACK_URL}`, + ssoLogout: () => `${AUTH_URL}/logout?redirect=${CALLBACK_URL}`, signIn: async (username, password) => logResponse(signInResponse), - signUp: async (username, password) => logResponse(signUpResponse), - verify: async (accessToken) => logResponse(verifyResponse), + verify: async (idToken) => logResponse(verifyResponse), + refresh: async (refreshToken) => logResponse(refreshResponse), setSignInResponse: (value) => { signInResponse = value; }, - setSignUpResponse: (value) => { signUpResponse = value; }, setVerifyResponse: (value) => { verifyResponse = value; }, + setRefreshResponse: (value) => { refreshResponse = value; }, } \ No newline at end of file diff --git a/src/services/__mocks__/uploadFile.js b/src/services/__mocks__/uploadFile.js index 57d934d..4443a17 100644 --- a/src/services/__mocks__/uploadFile.js +++ b/src/services/__mocks__/uploadFile.js @@ -11,7 +11,7 @@ export const getCancelToken = () => { return issuedCancelToken; } -export const uploadFile = async (file, token, onProgress, cancelToken, hash) => { +export const uploadFile = async (file, token, onProgress, cancelToken) => { if (!uploadFileDelay) return uploadFileResponse; onProgress(50); await delay(10000); diff --git a/src/services/auth.js b/src/services/auth.js index 27a340f..454f3dc 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -1,34 +1,34 @@ -const AUTH_URL = process.env.REACT_APP_AUTH_SERVICE_URL || "https://dev-auth.fiskerdps.com"; +const AUTH_URL = process.env.REACT_APP_AUTH_SERVICE_URL || "https://gw-dev.fiskerdps.com/compute_auth"; +const CALLBACK_URL = process.env.REACT_APP_AUTH_CALLBACK_URL || "https://dev-ota-admin.fiskerdps.com"; const auth = { - signIn: (username, password) => fetch(`${AUTH_URL}/auth/login`, { + ssoAuthorize: () => `${AUTH_URL}/authorize?redirect=${CALLBACK_URL}`, + ssoLogout: () => `${AUTH_URL}/logout?redirect=${CALLBACK_URL}`, + signIn: (code) => fetch(`${AUTH_URL}/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - username, - password, + code, + redirect: CALLBACK_URL, }) }).then((response) => response.json()), - signUp: (username, password) => fetch(`${AUTH_URL}/auth/register`, { + verify: (idToken) => fetch(`${AUTH_URL}/verify`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - username, - password, - }) + body: JSON.stringify({ token: idToken }) }).then((response) => response.json()), - verify: (accessToken) => fetch(`${AUTH_URL}/auth/verify`, { + refresh: (refreshToken) => fetch(`${AUTH_URL}/refresh`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ token: accessToken }) + body: JSON.stringify({ refresh_token: refreshToken }) }).then((response) => response.json()), }; diff --git a/src/services/auth.test.js b/src/services/auth.test.js new file mode 100644 index 0000000..bda2c35 --- /dev/null +++ b/src/services/auth.test.js @@ -0,0 +1,23 @@ +import auth from "./auth"; + +const testAuthURL = (url, endpoint) => { + const u = new URL(url); + const path = u.pathname.split("/"); + + expect(u.protocol).toMatch(/^http/); + expect(u.host).toBeTruthy(); + expect(path[path.length - 1]).toEqual(endpoint); + expect(u.searchParams.get("redirect")).toBeTruthy(); +}; + +describe("Auth service", () => { + describe("Auth URLs", () => { + it("Authorize URL", () => { + testAuthURL(auth.ssoAuthorize(), "authorize"); + }); + + it("Logout URL", () => { + testAuthURL(auth.ssoLogout(), "logout"); + }); + }); +}); \ No newline at end of file diff --git a/src/setupTests.js b/src/setupTests.js index 8f2609b..1dd407a 100644 --- a/src/setupTests.js +++ b/src/setupTests.js @@ -2,4 +2,4 @@ // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; +import "@testing-library/jest-dom"; From 2e1f4a7a7cca2566439457d1bd8fb24779ab6c26 Mon Sep 17 00:00:00 2001 From: John Wu <76966357+jwu-fisker@users.noreply.github.com> Date: Thu, 11 Mar 2021 12:53:29 -0800 Subject: [PATCH 13/28] Change main UI layout and add VINs to add and upload forms (#16) * Add new upload update package form Add new add vehicle form Add new side menu layout Add new toolbar layout Update and add unit tests * Enable add get and add vehicles * Integration issues with ota_update service * Update get vehicle JSON format * Fix related unit test Add release notes field * Add StatusContext to display error and status messages --- package.json | 1 + src/components/App/App.test.js | 23 +- .../App/__snapshots__/App.test.js.snap | 1557 +++++++++++++++-- src/components/App/index.jsx | 17 +- src/components/Contexts/FileUploadContext.jsx | 51 +- .../Contexts/FileUploadContext.test.jsx | 86 +- src/components/Contexts/StatusContext.jsx | 20 + src/components/Contexts/UserContext.jsx | 13 +- src/components/Contexts/UserContext.test.jsx | 27 +- src/components/Contexts/VehicleContext.jsx | 60 + .../Contexts/VehicleContext.test.jsx | 142 ++ .../Contexts/__mocks__/FileUploadContext.jsx | 5 + .../Contexts/__mocks__/VehicleContext.jsx | 24 + .../FileUploadForm/FileUploadForm.test.js | 10 +- .../__snapshots__/FileUploadForm.test.js.snap | 250 ++- src/components/FileUploadForm/index.jsx | 229 ++- src/components/Layouts/MenuDrawer.jsx | 105 ++ src/components/Layouts/SideMenu.jsx | 18 + src/components/ListItemLink.jsx | 35 + src/components/MessageBar.jsx | 12 +- src/components/Routes/ProtectedRoute.jsx | 7 +- src/components/Routes/SiteRoutes.jsx | 43 +- .../__snapshots__/SSOForm.test.js.snap | 48 +- src/components/SSOForm/index.jsx | 36 +- src/components/VehicleAddForm/index.jsx | 77 + src/components/menuItemStyle.jsx | 10 + src/components/useStyles.jsx | 87 + src/services/__mocks__/uploadFile.js | 2 +- src/services/__mocks__/vehicles.js | 17 + src/services/uploadFile.js | 11 +- src/services/vehicles.js | 20 + 31 files changed, 2666 insertions(+), 377 deletions(-) create mode 100644 src/components/Contexts/StatusContext.jsx create mode 100644 src/components/Contexts/VehicleContext.jsx create mode 100644 src/components/Contexts/VehicleContext.test.jsx create mode 100644 src/components/Contexts/__mocks__/VehicleContext.jsx create mode 100644 src/components/Layouts/MenuDrawer.jsx create mode 100644 src/components/Layouts/SideMenu.jsx create mode 100644 src/components/ListItemLink.jsx create mode 100644 src/components/VehicleAddForm/index.jsx create mode 100644 src/components/menuItemStyle.jsx create mode 100644 src/services/__mocks__/vehicles.js create mode 100644 src/services/vehicles.js diff --git a/package.json b/package.json index 789b9ca..6441d25 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@testing-library/react": "^11.2.2", "@testing-library/user-event": "^12.6.0", "axios": "^0.21.1", + "clsx": "^1.1.1", "material-ui-dropzone": "^3.5.0", "react": "^17.0.1", "react-dom": "^17.0.1", diff --git a/src/components/App/App.test.js b/src/components/App/App.test.js index 150a38a..c816b0d 100644 --- a/src/components/App/App.test.js +++ b/src/components/App/App.test.js @@ -1,5 +1,6 @@ jest.mock("../Contexts/UserContext"); jest.mock("../Contexts/FileUploadContext"); +jest.mock("../Contexts/VehicleContext"); import { render, screen, cleanup, waitForElementToBeRemoved } from "@testing-library/react"; import { setToken } from "../Contexts/UserContext"; @@ -26,27 +27,40 @@ describe("App", () => { it("Route / unauthenticated", async () => { const container = await renderRoute("/"); - expect(container.querySelector("h1").innerHTML).toEqual("Fisker OTA Portal"); + expect(container.querySelector("span.MuiButton-label").innerHTML).toEqual("Sign In"); expect(container).toMatchSnapshot(); }); it("Route /home unauthenticated", async () => { const container = await renderRoute("/home"); - expect(container.querySelector("h1").innerHTML).toEqual("Fisker OTA Portal"); + expect(container.querySelector("span.MuiButton-label").innerHTML).toEqual("Sign In"); + expect(container).toMatchSnapshot(); + }); + + it("Route /vehicle-add unauthenticated", async () => { + const container = await renderRoute("/vehicle-add"); + expect(container.querySelector("span.MuiButton-label").innerHTML).toEqual("Sign In"); expect(container).toMatchSnapshot(); }); it("Route / authenticated", async () => { setToken(TEST_TOKEN); const container = await renderRoute("/"); - expect(container.querySelector("h1").innerHTML).toEqual("Upload file"); + expect(container.querySelector("h1").innerHTML).toEqual("Upload Update Package"); expect(container).toMatchSnapshot(); }); it("Route /home authenticated", async () => { setToken(TEST_TOKEN); const container = await renderRoute("/home"); - expect(container.querySelector("h1").innerHTML).toEqual("Upload file"); + expect(container.querySelector("h1").innerHTML).toEqual("Upload Update Package"); + expect(container).toMatchSnapshot(); + }); + + it("Route /vehicle-add authenticated", async () => { + setToken(TEST_TOKEN); + const container = await renderRoute("/vehicle-add"); + expect(container.querySelector("h1").innerHTML).toEqual("Add Vehicle"); expect(container).toMatchSnapshot(); }); @@ -62,5 +76,4 @@ describe("App", () => { expect(container.querySelector("h1").innerHTML).toEqual("Page Not Found"); expect(container).toMatchSnapshot(); }); - }) \ No newline at end of file diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap index 21b2926..f2b0a88 100644 --- a/src/components/App/__snapshots__/App.test.js.snap +++ b/src/components/App/__snapshots__/App.test.js.snap @@ -5,81 +5,440 @@ exports[`App Route / authenticated 1`] = `
-
-
-

- Upload file -

-
-
- - -
-
+ + + + + +
+ Fisker OTA Portal +
+
+ + -
+
+
+
+
+
+
+

+ Upload Update Package +

+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+