Customer can now login and the context get's updated in the correct way. Modularized the types in @api
This commit is contained in:
parent
aa444c4e33
commit
c7f388f786
@ -30,6 +30,7 @@
|
||||
"dayjs": "^1.11.6",
|
||||
"final-form": "^4.20.7",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"ignore-loader": "^0.1.2",
|
||||
"markdown-to-jsx": "^7.1.7",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"react": "^18.2.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
import axios from "axios";
|
||||
|
||||
export default axios.create({
|
||||
export const axiosInstance = axios.create({
|
||||
baseURL: "http://localhost:5276",
|
||||
timeout: 1000,
|
||||
params : {
|
||||
|
1
src/api/declarations.d.ts
vendored
1
src/api/declarations.d.ts
vendored
@ -1 +0,0 @@
|
||||
declare type ApiToken = string; // QUESTION: does this make sense, to have this declaration here since we use is in multiple files?
|
32
src/api/getCustomerInfo.ts
Normal file
32
src/api/getCustomerInfo.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { axiosInstance } from "./axiosInstance";
|
||||
import { ApiToken, CustomerInfo } from "./types";
|
||||
|
||||
const API_TOKEN_NOT_FOUND_ERROR_CODE = 16;
|
||||
|
||||
/**
|
||||
* Get the data of customer
|
||||
*
|
||||
* @param token The token, given from WOnD, of the customer
|
||||
* @returns the data of the customer
|
||||
*/
|
||||
export async function getCustomerInfo(token: ApiToken, id: number): Promise<CustomerInfo> {
|
||||
|
||||
const res = await axiosInstance.get("/getCustomer", {
|
||||
params: { token, id }
|
||||
})
|
||||
|
||||
if (res.data.getCustomer.result == "ERROR"){
|
||||
const code: number = res.data.getCustomer.errorCode;
|
||||
// if (code == API_TOKEN_NOT_FOUND_ERROR_CODE)
|
||||
// navigate('/')
|
||||
const msg: string = res.data.getCustomer.message;
|
||||
throw new Error(`Couldn't get user info\nError code: ${code}\nError message: ${msg}`);
|
||||
}
|
||||
|
||||
const customerInfo: CustomerInfo = {
|
||||
name: res.data.getCustomer.customer.description, // yes i know...
|
||||
balance: res.data.getCustomer.customer.prepayBalanceCash
|
||||
}
|
||||
|
||||
return customerInfo;
|
||||
}
|
3
src/api/index.ts
Normal file
3
src/api/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./isTokenValid";
|
||||
export * from "./login";
|
||||
export * from "./getCustomerInfo";
|
@ -1,4 +1,5 @@
|
||||
import axiosInstance from "./axiosInstance";
|
||||
import { axiosInstance } from "./axiosInstance";
|
||||
import { ApiToken } from "./types";
|
||||
|
||||
|
||||
const API_INVALID_SHOP_ID_CODE = 608;
|
||||
@ -9,7 +10,7 @@ const API_INVALID_SHOP_ID_CODE = 608;
|
||||
* @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> {
|
||||
export 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.
|
||||
@ -19,12 +20,10 @@ export default async function isTokenValid(token: ApiToken): Promise<boolean> {
|
||||
|
||||
// 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
|
||||
}
|
||||
})
|
||||
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!");
|
||||
|
@ -1,20 +1,5 @@
|
||||
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;
|
||||
import { axiosInstance } from "./axiosInstance";
|
||||
import { ApiToken, LoginResponse, FailedLoginError } from "./types";
|
||||
|
||||
/**
|
||||
*
|
||||
@ -22,15 +7,16 @@ class FailedLoginError extends Error{
|
||||
* @param password the password of the user
|
||||
* @returns
|
||||
*/
|
||||
export default async function login(user: string, password: string): Promise<ApiToken>{
|
||||
// const user = useContext(userContext);
|
||||
export async function login(user: string, password: string): Promise<LoginResponse>{
|
||||
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;
|
||||
const token: ApiToken = res.data.login.customerProperties.token;
|
||||
const id: number = res.data.login.customerProperties.id;
|
||||
return { token, id };
|
||||
}
|
||||
|
||||
export { FailedLoginError };
|
1
src/api/types/ApiToken.d.ts
vendored
Normal file
1
src/api/types/ApiToken.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export type ApiToken = string;
|
4
src/api/types/CustomerInfo.d.ts
vendored
Normal file
4
src/api/types/CustomerInfo.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
export type CustomerInfo = {
|
||||
name: string;
|
||||
balance: number;
|
||||
};
|
8
src/api/types/FailedLoginError.ts
Normal file
8
src/api/types/FailedLoginError.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export class FailedLoginError extends Error {
|
||||
code: number;
|
||||
constructor(msg: string, code: number) {
|
||||
super(msg);
|
||||
this.code = code;
|
||||
Object.setPrototypeOf(this, FailedLoginError.prototype);
|
||||
}
|
||||
}
|
6
src/api/types/LoginResponse.d.ts
vendored
Normal file
6
src/api/types/LoginResponse.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
import { ApiToken } from "./ApiToken";
|
||||
|
||||
export type LoginResponse = {
|
||||
token: ApiToken;
|
||||
id: number;
|
||||
};
|
4
src/api/types/index.ts
Normal file
4
src/api/types/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./ApiToken.d"
|
||||
export * from "./CustomerInfo.d"
|
||||
export * from "./LoginResponse.d"
|
||||
export * from "./FailedLoginError"
|
@ -6,7 +6,7 @@ type ErrorProps = {
|
||||
err?: Error
|
||||
}
|
||||
|
||||
export default function ErrorPage({err} : ErrorProps) {
|
||||
export function ErrorPage({err} : ErrorProps) {
|
||||
const error: any = err || useRouteError();
|
||||
console.error(error);
|
||||
|
||||
|
@ -22,7 +22,7 @@ const schema = yup
|
||||
})
|
||||
.required();
|
||||
|
||||
export default function ForgotPassword() {
|
||||
export function ForgotPassword() {
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
register,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext } from "react";
|
||||
import React, { useContext, useEffect } from "react";
|
||||
import { Card, Button, TextField } from "@mui/material";
|
||||
import "@scss/login.scss";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
@ -7,7 +7,9 @@ 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";
|
||||
import { FailedLoginError } from "@api/types/FailedLoginError";
|
||||
import { LoginResponse } from "@api/types";
|
||||
import { login } from "@api";
|
||||
|
||||
type Inputs = {
|
||||
email: string;
|
||||
@ -25,7 +27,7 @@ const schema = yup
|
||||
})
|
||||
.required();
|
||||
|
||||
export default function Login() {
|
||||
export function Login() {
|
||||
const user = useContext(userContext);
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
@ -58,6 +60,7 @@ export default function Login() {
|
||||
name: "password",
|
||||
id: "password",
|
||||
label: "Password",
|
||||
type: "password",
|
||||
error: !!errors.password,
|
||||
helperText: errors?.password?.message,
|
||||
};
|
||||
@ -66,18 +69,27 @@ export default function Login() {
|
||||
console.warn("There will be an API call now with the following data");
|
||||
console.table(data);
|
||||
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("/");
|
||||
const res: LoginResponse = await login(data.email, data.password);
|
||||
console.log("Response of login:", res);
|
||||
user.setToken(res.token);
|
||||
user.setId(res.id);
|
||||
console.log("User context after login:", user)
|
||||
window.localStorage.setItem("token", res.token); // this could be done with a hook (right?)
|
||||
window.localStorage.setItem("customerId", res.id.toString()); // this could be done with a hook (right?)
|
||||
}catch(err){
|
||||
|
||||
if (err instanceof FailedLoginError)
|
||||
alert(`Failed to login user.\nError code: '${err.code}', error message: '${err.message}'`)
|
||||
alert(`Failed to login user.\nError code: '${err.code}', error message: '${err.message}'\n\nYes, I know I should handle it with something else than an alert, but for now I'm making the thing work When it fully works, I'll make it prettier :)`)
|
||||
else
|
||||
alert('Failed to make api call');
|
||||
alert(`Failed to make api call\nError message: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (user.token)
|
||||
navigate("/");
|
||||
}, [user.token])
|
||||
|
||||
return (
|
||||
<main className="cardContainer">
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
|
@ -22,7 +22,7 @@ const schema = yup
|
||||
})
|
||||
.required();
|
||||
|
||||
export default function Reset() {
|
||||
export function Reset() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext, useEffect } from "react";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import "@scss/transactions.scss";
|
||||
import Table from "@components/Transactions/Table"
|
||||
import userContext from "@ts/userContext"
|
||||
@ -15,6 +15,8 @@ import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider/L
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||
import { getValue } from "@mui/system";
|
||||
import { CustomerInfo } from "@api/types";
|
||||
import { getCustomerInfo } from "@api";
|
||||
|
||||
|
||||
|
||||
@ -30,7 +32,7 @@ type Inputs = {
|
||||
const schema = yup
|
||||
.object()
|
||||
.shape({
|
||||
startDate: yup.date().nullable().default(() => {return null}).typeError("Invalid date"),
|
||||
startDate: yup.date().nullable().typeError("Invalid date"),
|
||||
endDate: yup.date().nullable().typeError("Invalid date"),
|
||||
minAmount: yup.number().positive(),
|
||||
maxAmount: yup.number().positive(),
|
||||
@ -39,8 +41,20 @@ const schema = yup
|
||||
.required();
|
||||
|
||||
|
||||
export default function Transactions() {
|
||||
const user = useContext(userContext)
|
||||
export function Transactions() {
|
||||
const user = useContext(userContext);
|
||||
const [customerInfo, setCustomerInfo] = useState<CustomerInfo>({name: "John Smith", balance: -1});
|
||||
|
||||
async function fetchData() {
|
||||
if (!user.token)
|
||||
return;
|
||||
const info = await getCustomerInfo(user.token, user.id);
|
||||
setCustomerInfo(info);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [user.token])
|
||||
|
||||
const {
|
||||
register,
|
||||
@ -53,7 +67,7 @@ export default function Transactions() {
|
||||
|
||||
|
||||
const { ref: startDateRef, ...startDateRegisterProps } = register("startDate");
|
||||
console.log(startDateRegisterProps)
|
||||
// console.log(startDateRegisterProps)
|
||||
|
||||
const startDatePickerProps = {
|
||||
...startDateRegisterProps,
|
||||
@ -116,7 +130,7 @@ export default function Transactions() {
|
||||
</Typography>
|
||||
<Box className="box right">
|
||||
<Link variant="h6" className="link" href="/">
|
||||
John Smith
|
||||
{customerInfo.name} (CHF {customerInfo.balance})
|
||||
</Link>
|
||||
<AccountCircleIcon className="icon" />
|
||||
</Box>
|
||||
|
@ -1,9 +1,7 @@
|
||||
import Login from "@components/Login";
|
||||
import ForgotPassword from "@components/ForgotPassword";
|
||||
import Reset from "@components/Reset";
|
||||
import ErrorPage from "@components/ErrorPage";
|
||||
import Transactions from "@components/Transactions";
|
||||
import MetadataSetter from "@components/lib/MetadataSetter";
|
||||
import AuthComponent from "@components/lib/AuthComponent";
|
||||
|
||||
export { Login, ForgotPassword, Reset, ErrorPage, Transactions, MetadataSetter, AuthComponent };
|
||||
export * from "@components/Login";
|
||||
export * from "@components/ForgotPassword";
|
||||
export * from "@components/Reset";
|
||||
export * from "@components/ErrorPage";
|
||||
export * from "@components/Transactions";
|
||||
export * from "@components/lib/MetadataSetter";
|
||||
export * from "@components/lib/AuthComponent";
|
@ -1,23 +1,28 @@
|
||||
import isTokenValid from "@api/isTokenValid";
|
||||
import ErrorPage from "@components/ErrorPage";
|
||||
import React, { useEffect } from "react";
|
||||
import { isTokenValid } from "@api";
|
||||
import { ApiToken } from "@api/types";
|
||||
import userContext from "@ts/userContext";
|
||||
import React, { useContext, useEffect } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
|
||||
type AuthProps = {
|
||||
children: React.ReactNode,
|
||||
}
|
||||
export default function AuthComponent({ children }: AuthProps) {
|
||||
export function AuthComponent({ children }: AuthProps) {
|
||||
const navigate = useNavigate();
|
||||
const user = useContext(userContext);
|
||||
|
||||
async function checkIfCustomerLogged(token: ApiToken){
|
||||
const isLogged: boolean = token != null && await isTokenValid(user.token);
|
||||
|
||||
if (!isLogged){
|
||||
navigate("/login", { replace: true });
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// navigate needs to be wrapped in a useEffect so that it gets executed after the component is mounted. Otherwise it doesn't redirect
|
||||
(async () => {
|
||||
const token: ApiToken = window.localStorage.getItem("token");
|
||||
const isLogged: boolean = token != null && await isTokenValid(token);
|
||||
if (!isLogged)
|
||||
navigate("/login", { replace: true });
|
||||
})()
|
||||
}, [])
|
||||
checkIfCustomerLogged(user.token);
|
||||
}, [user.token])
|
||||
|
||||
// by default return the children, the effect will check if the user needs to be redirected
|
||||
return <>{children}</>;
|
||||
|
@ -1,16 +1,17 @@
|
||||
import React, { useEffect } from "react";
|
||||
import {AuthComponent} from "@components";
|
||||
import { AuthComponent } from "@components";
|
||||
|
||||
type MetadataProps = {
|
||||
children: JSX.Element | JSX.Element[],
|
||||
title: string,
|
||||
bodyClass: string,
|
||||
needsAuth? : boolean
|
||||
needsAuth?: boolean
|
||||
}
|
||||
|
||||
export default function MetadataSetter({children, title, bodyClass, needsAuth = false}: MetadataProps){
|
||||
export function MetadataSetter({ children, title, bodyClass, needsAuth = false }: MetadataProps) {
|
||||
|
||||
useEffect(() => {
|
||||
document.title= title;
|
||||
document.title = title;
|
||||
document.body.classList.add(bodyClass);
|
||||
}, [title])
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client";
|
||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||
import { MetadataSetter, ErrorPage, Login, ForgotPassword, Reset, Transactions } from "@components";
|
||||
import UserContext from '@ts/userContext'
|
||||
import { ApiToken } from "@api/types";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@ -41,8 +42,11 @@ const router = createBrowserRouter([
|
||||
|
||||
|
||||
const App = function () {
|
||||
const [token, setToken] = useState<string>()
|
||||
const userContextValue = { token, setToken }
|
||||
|
||||
const [token, setToken] = useState<ApiToken>(window.localStorage.getItem("token"));
|
||||
const [id, setId] = useState<number>(Number.parseInt(window.localStorage.getItem("customerId")));
|
||||
const userContextValue = { token, setToken, id, setId }
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={userContextValue}>
|
||||
<RouterProvider router={router} />
|
||||
|
@ -2,5 +2,7 @@ import { createContext } from 'react'
|
||||
|
||||
export default createContext({
|
||||
token : "Unknown",
|
||||
setToken: (newToken: string) => {}
|
||||
setToken: (newToken: string) => {},
|
||||
id : -1,
|
||||
setId: (newId: number) => {},
|
||||
})
|
@ -14,6 +14,7 @@
|
||||
"@components/*": ["src/components/*"],
|
||||
"@scss/*": ["src/scss/*"],
|
||||
"@ts/*": ["src/ts/*"],
|
||||
"@api": ["src/api"],
|
||||
"@api/*": ["src/api/*"],
|
||||
"@theme/*": ["themes/onepirate/*"],
|
||||
}
|
||||
|
@ -25,9 +25,13 @@ module.exports = (env, argv) => {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/
|
||||
test: /\.tsx?$/,
|
||||
loader: 'ts-loader',
|
||||
exclude: /node_modules|\.d\.ts$/
|
||||
},
|
||||
{
|
||||
test: /\.d\.ts$/,
|
||||
loader: 'ignore-loader'
|
||||
},
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
|
Loading…
Reference in New Issue
Block a user