The customer can now login to the actual TCPOS backend by making the API call and storing the token in localstorage

This commit is contained in:
Arnaud Fauconnet 2022-11-03 13:24:52 +01:00
parent a1072bff05
commit aa444c4e33
11 changed files with 111 additions and 22 deletions

View File

@ -24,6 +24,7 @@
"@types/react": "^18.0.21", "@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"axios": "^1.1.3",
"babel-loader": "^8.2.5", "babel-loader": "^8.2.5",
"css-loader": "^6.7.1", "css-loader": "^6.7.1",
"dayjs": "^1.11.6", "dayjs": "^1.11.6",

9
src/api/axiosInstance.ts Normal file
View File

@ -0,0 +1,9 @@
import axios from "axios";
export default axios.create({
baseURL: "http://localhost:5276",
timeout: 1000,
params : {
shopId: 10,
}
})

1
src/api/declarations.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare type ApiToken = string; // QUESTION: does this make sense, to have this declaration here since we use is in multiple files?

35
src/api/isTokenValid.ts Normal file
View File

@ -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<boolean> {
// 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);
}

36
src/api/login.ts Normal file
View File

@ -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<ApiToken>{
// 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 };

View File

@ -7,6 +7,7 @@ import * as yup from "yup";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import userContext from "@ts/userContext" import userContext from "@ts/userContext"
import Typography from "@theme/modules/components/Typography"; import Typography from "@theme/modules/components/Typography";
import login , { FailedLoginError } from "@api/login";
type Inputs = { type Inputs = {
email: string; email: string;
@ -35,10 +36,6 @@ export default function Login() {
resolver: yupResolver(schema), resolver: yupResolver(schema),
}); });
function hash(s: string) {
return `hash of '${s}' to be definied`;
}
const { ref: emailRef, ...emailRegisterProps } = register("email"); const { ref: emailRef, ...emailRegisterProps } = register("email");
const emailProps = { const emailProps = {
@ -65,12 +62,20 @@ export default function Login() {
helperText: errors?.password?.message, 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"); 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); 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 ( return (

View File

@ -116,7 +116,7 @@ export default function Transactions() {
</Typography> </Typography>
<Box className="box right"> <Box className="box right">
<Link variant="h6" className="link" href="/"> <Link variant="h6" className="link" href="/">
{user.name} John Smith
</Link> </Link>
<AccountCircleIcon className="icon" /> <AccountCircleIcon className="icon" />
</Box> </Box>

View File

@ -1,3 +1,4 @@
import isTokenValid from "@api/isTokenValid";
import ErrorPage from "@components/ErrorPage"; import ErrorPage from "@components/ErrorPage";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useNavigate, useSearchParams } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
@ -7,17 +8,17 @@ type AuthProps = {
} }
export default function AuthComponent({ children }: AuthProps) { export default function AuthComponent({ children }: AuthProps) {
const navigate = useNavigate(); const navigate = useNavigate();
const searchParams = useSearchParams()[0];
const isLogged = searchParams.has("logged");
useEffect(() => { useEffect(() => {
// navigate needs to be wrapped in a useEffect so that it gets executed after the component is mounted. Otherwise it doesn't redirect // 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) (async () => {
navigate("/login", { replace: true }); const token: ApiToken = window.localStorage.getItem("token");
}, [isLogged]) const isLogged: boolean = token != null && await isTokenValid(token);
if (!isLogged)
navigate("/login", { replace: true });
})()
}, [])
if (isLogged) // by default return the children, the effect will check if the user needs to be redirected
return <>{children}</>; return <>{children}</>;
// else
// return <ErrorPage err={new Error("You are not logged in and you should have been redirected to the login page. Something went wrong.")}/>;
} }

View File

@ -41,8 +41,8 @@ const router = createBrowserRouter([
const App = function () { const App = function () {
const [name, setName] = useState("John Smith") const [token, setToken] = useState<string>()
const userContextValue = { name, setName } const userContextValue = { token, setToken }
return ( return (
<UserContext.Provider value={userContextValue}> <UserContext.Provider value={userContextValue}>
<RouterProvider router={router} /> <RouterProvider router={router} />

View File

@ -1,6 +1,6 @@
import { createContext } from 'react' import { createContext } from 'react'
export default createContext({ export default createContext({
name : "Unknown", token : "Unknown",
setName: (name: string) => {} setToken: (newToken: string) => {}
}) })

View File

@ -14,6 +14,7 @@
"@components/*": ["src/components/*"], "@components/*": ["src/components/*"],
"@scss/*": ["src/scss/*"], "@scss/*": ["src/scss/*"],
"@ts/*": ["src/ts/*"], "@ts/*": ["src/ts/*"],
"@api/*": ["src/api/*"],
"@theme/*": ["themes/onepirate/*"], "@theme/*": ["themes/onepirate/*"],
} }
}, },