From aa444c4e3376dfda50e59feb7b87cc64b63e40e4 Mon Sep 17 00:00:00 2001 From: Arnaud Fauconnet Date: Thu, 3 Nov 2022 13:24:52 +0100 Subject: [PATCH] The customer can now login to the actual TCPOS backend by making the API call and storing the token in localstorage --- package.json | 1 + src/api/axiosInstance.ts | 9 +++++++ src/api/declarations.d.ts | 1 + src/api/isTokenValid.ts | 35 +++++++++++++++++++++++++++ src/api/login.ts | 36 ++++++++++++++++++++++++++++ src/components/Login.tsx | 21 +++++++++------- src/components/Transactions.tsx | 2 +- src/components/lib/AuthComponent.tsx | 19 ++++++++------- src/index.tsx | 4 ++-- src/ts/userContext.ts | 4 ++-- tsconfig.json | 1 + 11 files changed, 111 insertions(+), 22 deletions(-) create mode 100644 src/api/axiosInstance.ts create mode 100644 src/api/declarations.d.ts create mode 100644 src/api/isTokenValid.ts create mode 100644 src/api/login.ts diff --git a/package.json b/package.json index ac8a9ad..4681ee6 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", "@types/react-router-dom": "^5.3.3", + "axios": "^1.1.3", "babel-loader": "^8.2.5", "css-loader": "^6.7.1", "dayjs": "^1.11.6", diff --git a/src/api/axiosInstance.ts b/src/api/axiosInstance.ts new file mode 100644 index 0000000..6cf6478 --- /dev/null +++ b/src/api/axiosInstance.ts @@ -0,0 +1,9 @@ +import axios from "axios"; + +export default axios.create({ + baseURL: "http://localhost:5276", + timeout: 1000, + params : { + shopId: 10, + } +}) \ No newline at end of file diff --git a/src/api/declarations.d.ts b/src/api/declarations.d.ts new file mode 100644 index 0000000..7eb0fc6 --- /dev/null +++ b/src/api/declarations.d.ts @@ -0,0 +1 @@ +declare type ApiToken = string; // QUESTION: does this make sense, to have this declaration here since we use is in multiple files? \ No newline at end of file diff --git a/src/api/isTokenValid.ts b/src/api/isTokenValid.ts new file mode 100644 index 0000000..2ffaac1 --- /dev/null +++ b/src/api/isTokenValid.ts @@ -0,0 +1,35 @@ +import axiosInstance from "./axiosInstance"; + + +const API_INVALID_SHOP_ID_CODE = 608; + +/** + * Check whether the token of the user is valid. + * + * @param token the token of the logged in customer + * @returns whether the user's token is still valid for the backend (aka if he is still logged in) + */ +export default async function isTokenValid(token: ApiToken): Promise { + // There is no official way to check if the token is still valid or not, + // so after asking Daniele Comes, he said to make a call to retrieveOrders + // with shopId of -1 so that the DB wouldn't be queried. + // If the answer is "Invalid field 'shopId'", then the token is valid. + // If it isn't, it will be picked up before checking the shopId and + // the answer will contain a certain variation of the message "Token is not valid" + + // Daniele Comes said he asked the WOnD team to make an endpoint to ask if the token is valid. + + let res = await axiosInstance.get("/retrieveOrders", { + params: { + token, + shopId: -1 + } + }) + + if (res.data.retrieveOrders.result != "ERROR") + throw new Error("The API somehow didn't answer ERROR when asking to retrieve orders from shop with id=-1... This is not normal!"); + + const errorCode: number = res.data.retrieveOrders.errorCode; + + return (errorCode == API_INVALID_SHOP_ID_CODE); +} \ No newline at end of file diff --git a/src/api/login.ts b/src/api/login.ts new file mode 100644 index 0000000..590b9ba --- /dev/null +++ b/src/api/login.ts @@ -0,0 +1,36 @@ +import axiosInstance from "./axiosInstance"; + +interface LoginResponse { + isLogged: boolean; + nextAction(): void; +} + +class FailedLoginError extends Error{ + code: number; + constructor (msg: string, code: number){ + super(msg); + this.code = code; + Object.setPrototypeOf(this, FailedLoginError.prototype) + } +} + +// type ApiToken = string; + +/** + * + * @param user the name of user logging in (if it's a customer, then it's his email) + * @param password the password of the user + * @returns + */ +export default async function login(user: string, password: string): Promise{ + // const user = useContext(userContext); + const res = await axiosInstance.get("/login", { params: { user, password } }); + if (res.data.login.result == "ERROR") { + const errCode: number = res.data.login.errorCode; + const errMsg: string = res.data.login.message; + throw new FailedLoginError(errMsg, errCode); + } + return res.data.login.customerProperties.token; +} + +export { FailedLoginError }; \ No newline at end of file diff --git a/src/components/Login.tsx b/src/components/Login.tsx index 860b738..fc582ed 100644 --- a/src/components/Login.tsx +++ b/src/components/Login.tsx @@ -7,6 +7,7 @@ import * as yup from "yup"; import { useForm } from "react-hook-form"; import userContext from "@ts/userContext" import Typography from "@theme/modules/components/Typography"; +import login , { FailedLoginError } from "@api/login"; type Inputs = { email: string; @@ -35,10 +36,6 @@ export default function Login() { resolver: yupResolver(schema), }); - function hash(s: string) { - return `hash of '${s}' to be definied`; - } - const { ref: emailRef, ...emailRegisterProps } = register("email"); const emailProps = { @@ -65,12 +62,20 @@ export default function Login() { helperText: errors?.password?.message, }; - function onSubmit(data: Inputs) { + async function onSubmit(data: Inputs) { console.warn("There will be an API call now with the following data"); - data = { ...data, password: hash(data.password) }; - user.setName(data.email); console.table(data); - setTimeout(() => navigate("/?logged"), 3000); + try { + const token: ApiToken = await login(data.email, data.password); + user.setToken(token); + window.localStorage.setItem("token", token); // this could be done with a hook (right?) + navigate("/"); + }catch(err){ + if (err instanceof FailedLoginError) + alert(`Failed to login user.\nError code: '${err.code}', error message: '${err.message}'`) + else + alert('Failed to make api call'); + } } return ( diff --git a/src/components/Transactions.tsx b/src/components/Transactions.tsx index 6370fa8..33b5dee 100644 --- a/src/components/Transactions.tsx +++ b/src/components/Transactions.tsx @@ -116,7 +116,7 @@ export default function Transactions() { - {user.name} + John Smith diff --git a/src/components/lib/AuthComponent.tsx b/src/components/lib/AuthComponent.tsx index c96e19d..27d46dc 100644 --- a/src/components/lib/AuthComponent.tsx +++ b/src/components/lib/AuthComponent.tsx @@ -1,3 +1,4 @@ +import isTokenValid from "@api/isTokenValid"; import ErrorPage from "@components/ErrorPage"; import React, { useEffect } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; @@ -7,17 +8,17 @@ type AuthProps = { } export default function AuthComponent({ children }: AuthProps) { const navigate = useNavigate(); - const searchParams = useSearchParams()[0]; - const isLogged = searchParams.has("logged"); useEffect(() => { // navigate needs to be wrapped in a useEffect so that it gets executed after the component is mounted. Otherwise it doesn't redirect - if (!isLogged) - navigate("/login", { replace: true }); - }, [isLogged]) + (async () => { + const token: ApiToken = window.localStorage.getItem("token"); + const isLogged: boolean = token != null && await isTokenValid(token); + if (!isLogged) + navigate("/login", { replace: true }); + })() + }, []) - if (isLogged) - return <>{children}; - // else - // return ; + // by default return the children, the effect will check if the user needs to be redirected + return <>{children}; } \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index 8606d7e..9922ab5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -41,8 +41,8 @@ const router = createBrowserRouter([ const App = function () { - const [name, setName] = useState("John Smith") - const userContextValue = { name, setName } + const [token, setToken] = useState() + const userContextValue = { token, setToken } return ( diff --git a/src/ts/userContext.ts b/src/ts/userContext.ts index c22201d..8d3bb21 100644 --- a/src/ts/userContext.ts +++ b/src/ts/userContext.ts @@ -1,6 +1,6 @@ import { createContext } from 'react' export default createContext({ - name : "Unknown", - setName: (name: string) => {} + token : "Unknown", + setToken: (newToken: string) => {} }) \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index e3defa5..09907cf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ "@components/*": ["src/components/*"], "@scss/*": ["src/scss/*"], "@ts/*": ["src/ts/*"], + "@api/*": ["src/api/*"], "@theme/*": ["themes/onepirate/*"], } },