Compare commits
No commits in common. "3bd38fdefad301547ecb6a58961b6a789b1163b0" and "a1072bff05962ea96e20c54af02309f86364a8de" have entirely different histories.
3bd38fdefa
...
a1072bff05
@ -24,20 +24,17 @@
|
|||||||
"@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",
|
||||||
"final-form": "^4.20.7",
|
"final-form": "^4.20.7",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
"ignore-loader": "^0.1.2",
|
|
||||||
"markdown-to-jsx": "^7.1.7",
|
"markdown-to-jsx": "^7.1.7",
|
||||||
"mini-css-extract-plugin": "^2.6.1",
|
"mini-css-extract-plugin": "^2.6.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-final-form": "^6.5.9",
|
"react-final-form": "^6.5.9",
|
||||||
"react-hook-form": "^7.38.0",
|
"react-hook-form": "^7.38.0",
|
||||||
"react-loading-icons": "^1.1.0",
|
|
||||||
"react-router-dom": "^6.4.2",
|
"react-router-dom": "^6.4.2",
|
||||||
"sass": "^1.55.0",
|
"sass": "^1.55.0",
|
||||||
"sass-loader": "^13.1.0",
|
"sass-loader": "^13.1.0",
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import axios from "axios";
|
|
||||||
|
|
||||||
export const axiosInstance = axios.create({
|
|
||||||
baseURL: "http://localhost:5276",
|
|
||||||
timeout: 5000,
|
|
||||||
params : {
|
|
||||||
shopId: 10,
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,37 +0,0 @@
|
|||||||
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 fetchCustomerInfo(token: ApiToken, id: number): Promise<CustomerInfo> {
|
|
||||||
|
|
||||||
function timeout(ms: number) {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms))
|
|
||||||
}
|
|
||||||
|
|
||||||
await timeout(3000);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
export * from "./isTokenValid";
|
|
||||||
export * from "./login";
|
|
||||||
export * from "./fetchCustomerInfo";
|
|
@ -1,35 +0,0 @@
|
|||||||
import { axiosInstance } from "./axiosInstance";
|
|
||||||
import { ApiToken } from "./types";
|
|
||||||
|
|
||||||
|
|
||||||
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 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;
|
|
||||||
// return false;
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
import { axiosInstance } from "./axiosInstance";
|
|
||||||
import { ApiToken, LoginResponse, FailedLoginError } from "./types";
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @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 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);
|
|
||||||
}
|
|
||||||
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
1
src/api/types/ApiToken.d.ts
vendored
@ -1 +0,0 @@
|
|||||||
export type ApiToken = string;
|
|
4
src/api/types/CustomerInfo.d.ts
vendored
4
src/api/types/CustomerInfo.d.ts
vendored
@ -1,4 +0,0 @@
|
|||||||
export type CustomerInfo = {
|
|
||||||
name: string;
|
|
||||||
balance: number;
|
|
||||||
};
|
|
@ -1,8 +0,0 @@
|
|||||||
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
6
src/api/types/LoginResponse.d.ts
vendored
@ -1,6 +0,0 @@
|
|||||||
import { ApiToken } from "./ApiToken";
|
|
||||||
|
|
||||||
export type LoginResponse = {
|
|
||||||
token: ApiToken;
|
|
||||||
id: number;
|
|
||||||
};
|
|
@ -1,4 +0,0 @@
|
|||||||
export * from "./ApiToken.d"
|
|
||||||
export * from "./CustomerInfo.d"
|
|
||||||
export * from "./LoginResponse.d"
|
|
||||||
export * from "./FailedLoginError"
|
|
@ -6,7 +6,7 @@ type ErrorProps = {
|
|||||||
err?: Error
|
err?: Error
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ErrorPage({err} : ErrorProps) {
|
export default function ErrorPage({err} : ErrorProps) {
|
||||||
const error: any = err || useRouteError();
|
const error: any = err || useRouteError();
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ const schema = yup
|
|||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export function ForgotPassword() {
|
export default function ForgotPassword() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useEffect } from "react";
|
import React, { useContext } from "react";
|
||||||
import { Card, Button, TextField } from "@mui/material";
|
import { Card, Button, TextField } from "@mui/material";
|
||||||
import "@scss/login.scss";
|
import "@scss/login.scss";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
@ -7,9 +7,6 @@ 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 { FailedLoginError } from "@api/types/FailedLoginError";
|
|
||||||
import { LoginResponse } from "@api/types";
|
|
||||||
import { login } from "@api";
|
|
||||||
|
|
||||||
type Inputs = {
|
type Inputs = {
|
||||||
email: string;
|
email: string;
|
||||||
@ -27,15 +24,9 @@ const schema = yup
|
|||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export function Login() {
|
export default function Login() {
|
||||||
const user = useContext(userContext);
|
const user = useContext(userContext);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (user.token)
|
|
||||||
navigate("/");
|
|
||||||
}, [user.token])
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
@ -44,6 +35,10 @@ export 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 = {
|
||||||
@ -66,29 +61,16 @@ export function Login() {
|
|||||||
name: "password",
|
name: "password",
|
||||||
id: "password",
|
id: "password",
|
||||||
label: "Password",
|
label: "Password",
|
||||||
type: "password",
|
|
||||||
error: !!errors.password,
|
error: !!errors.password,
|
||||||
helperText: errors?.password?.message,
|
helperText: errors?.password?.message,
|
||||||
};
|
};
|
||||||
|
|
||||||
async function onSubmit(data: Inputs) {
|
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);
|
||||||
try {
|
setTimeout(() => navigate("/?logged"), 3000);
|
||||||
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}'\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\nError message: ${err.message}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -22,7 +22,7 @@ const schema = yup
|
|||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export function Reset() {
|
export default function Reset() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import React, { useContext, useEffect, useState, MouseEvent } from "react";
|
import React, { useContext, useEffect } from "react";
|
||||||
import "@scss/transactions.scss";
|
import "@scss/transactions.scss";
|
||||||
import { ThreeDots as LoadingIcon } from "react-loading-icons";
|
|
||||||
import Table from "@components/Transactions/Table"
|
import Table from "@components/Transactions/Table"
|
||||||
import userContext from "@ts/userContext"
|
import userContext from "@ts/userContext"
|
||||||
import AppBar from "@theme/modules/components/AppBar";
|
import AppBar from "@theme/modules/components/AppBar";
|
||||||
import Toolbar from "@theme/modules/components/Toolbar";
|
import Toolbar from "@theme/modules/components/Toolbar";
|
||||||
import Typography from "@theme/modules/components/Typography";
|
import Typography from "@theme/modules/components/Typography";
|
||||||
import { Box, Link, TextFieldProps, TextField, Button } from "@mui/material";
|
import { Box, Link, TextFieldProps, TextField } from "@mui/material";
|
||||||
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
||||||
import { DesktopDatePicker } from '@mui/x-date-pickers/DesktopDatePicker';
|
import { DesktopDatePicker } from '@mui/x-date-pickers/DesktopDatePicker';
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
@ -16,8 +15,6 @@ import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider/L
|
|||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||||
import { getValue } from "@mui/system";
|
import { getValue } from "@mui/system";
|
||||||
import { CustomerInfo } from "@api/types";
|
|
||||||
import { useGetCustomerInfo } from "src/hooks/useGetCustomerInfo";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -33,7 +30,7 @@ type Inputs = {
|
|||||||
const schema = yup
|
const schema = yup
|
||||||
.object()
|
.object()
|
||||||
.shape({
|
.shape({
|
||||||
startDate: yup.date().nullable().typeError("Invalid date"),
|
startDate: yup.date().nullable().default(() => {return null}).typeError("Invalid date"),
|
||||||
endDate: yup.date().nullable().typeError("Invalid date"),
|
endDate: yup.date().nullable().typeError("Invalid date"),
|
||||||
minAmount: yup.number().positive(),
|
minAmount: yup.number().positive(),
|
||||||
maxAmount: yup.number().positive(),
|
maxAmount: yup.number().positive(),
|
||||||
@ -42,14 +39,8 @@ const schema = yup
|
|||||||
.required();
|
.required();
|
||||||
|
|
||||||
|
|
||||||
export function Transactions() {
|
export default function Transactions() {
|
||||||
const user = useContext(userContext);
|
const user = useContext(userContext)
|
||||||
const { customerInfo, isLoading, getCustomerInfo } = useGetCustomerInfo();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (user.token)
|
|
||||||
getCustomerInfo(user.token, user.id);
|
|
||||||
}, [user.token])
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@ -62,7 +53,7 @@ export function Transactions() {
|
|||||||
|
|
||||||
|
|
||||||
const { ref: startDateRef, ...startDateRegisterProps } = register("startDate");
|
const { ref: startDateRef, ...startDateRegisterProps } = register("startDate");
|
||||||
// console.log(startDateRegisterProps)
|
console.log(startDateRegisterProps)
|
||||||
|
|
||||||
const startDatePickerProps = {
|
const startDatePickerProps = {
|
||||||
...startDateRegisterProps,
|
...startDateRegisterProps,
|
||||||
@ -115,10 +106,6 @@ export function Transactions() {
|
|||||||
helperText: errors?.maxAmount?.message,
|
helperText: errors?.maxAmount?.message,
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRefreshButton(event: MouseEvent<HTMLElement>): void {
|
|
||||||
getCustomerInfo(user.token, user.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="tablePage">
|
<div id="tablePage">
|
||||||
<AppBar position="fixed" sx={{ background: "#28282a" }}>
|
<AppBar position="fixed" sx={{ background: "#28282a" }}>
|
||||||
@ -127,13 +114,9 @@ export function Transactions() {
|
|||||||
<Typography variant="h3" >
|
<Typography variant="h3" >
|
||||||
Transactions
|
Transactions
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button sx={{marginLeft: 5}} variant="contained" onClick={handleRefreshButton}>
|
|
||||||
Refresh
|
|
||||||
</Button>
|
|
||||||
<Box className="box right">
|
<Box className="box right">
|
||||||
<Link variant="h6" className="link userName" href="/">
|
<Link variant="h6" className="link" href="/">
|
||||||
{isLoading ? <LoadingIcon height=".5em"/> : customerInfo.name}
|
{user.name}
|
||||||
{/* {customerInfo.name} (CHF {customerInfo.balancE}) */}
|
|
||||||
</Link>
|
</Link>
|
||||||
<AccountCircleIcon className="icon" />
|
<AccountCircleIcon className="icon" />
|
||||||
</Box>
|
</Box>
|
||||||
@ -144,26 +127,18 @@ export function Transactions() {
|
|||||||
<Typography variant="h5">
|
<Typography variant="h5">
|
||||||
Filters
|
Filters
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box className="box">
|
|
||||||
<Typography variant="h6">
|
<Typography variant="h6">
|
||||||
Dates
|
Dates
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box className="inputs-box dates">
|
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
<DesktopDatePicker {...startDatePickerProps} />
|
<DesktopDatePicker {...startDatePickerProps} />
|
||||||
<DesktopDatePicker {...endDatePickerProps} />
|
<DesktopDatePicker {...endDatePickerProps} />
|
||||||
</LocalizationProvider>
|
</LocalizationProvider>
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Box className="box">
|
|
||||||
<Typography variant="h6">
|
<Typography variant="h6">
|
||||||
Transactions
|
Transactions
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box className="inputs-box transactions">
|
|
||||||
<TextField {...minAmountProps}></TextField>
|
<TextField {...minAmountProps}></TextField>
|
||||||
<TextField {...maxAmountProps}></TextField>
|
<TextField {...maxAmountProps}></TextField>
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</form>
|
</form>
|
||||||
</aside>
|
</aside>
|
||||||
<main>
|
<main>
|
||||||
|
@ -49,9 +49,9 @@ export default () => {
|
|||||||
<TableCell component="th" scope="row">
|
<TableCell component="th" scope="row">
|
||||||
{row.date.toLocaleDateString()}
|
{row.date.toLocaleDateString()}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="description">{row.description}</TableCell>
|
<TableCell>{row.description}</TableCell>
|
||||||
<TableCell align="right" className={"amount " + (row.amount < 0 ? "negative" : "positive")}>{CHF_currency(row.amount)}</TableCell>
|
<TableCell align="right" className={"amount " + (row.amount < 0 ? "negative" : "positive")}>{CHF_currency(row.amount)}</TableCell>
|
||||||
<TableCell align="right" className="total">{CHF_currency(row.total)}</TableCell>
|
<TableCell align="right">{CHF_currency(row.total)}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
export * from "@components/Login";
|
import Login from "@components/Login";
|
||||||
export * from "@components/ForgotPassword";
|
import ForgotPassword from "@components/ForgotPassword";
|
||||||
export * from "@components/Reset";
|
import Reset from "@components/Reset";
|
||||||
export * from "@components/ErrorPage";
|
import ErrorPage from "@components/ErrorPage";
|
||||||
export * from "@components/Transactions";
|
import Transactions from "@components/Transactions";
|
||||||
export * from "@components/lib/MetadataSetter";
|
import MetadataSetter from "@components/lib/MetadataSetter";
|
||||||
export * from "@components/lib/AuthComponent";
|
import AuthComponent from "@components/lib/AuthComponent";
|
||||||
|
|
||||||
|
export { Login, ForgotPassword, Reset, ErrorPage, Transactions, MetadataSetter, AuthComponent };
|
@ -1,34 +1,23 @@
|
|||||||
import { isTokenValid } from "@api";
|
import ErrorPage from "@components/ErrorPage";
|
||||||
import { ApiToken } from "@api/types";
|
import React, { useEffect } from "react";
|
||||||
import userContext from "@ts/userContext";
|
|
||||||
import React, { useContext, useEffect } from "react";
|
|
||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
type AuthProps = {
|
type AuthProps = {
|
||||||
children: React.ReactNode,
|
children: React.ReactNode,
|
||||||
}
|
}
|
||||||
export function AuthComponent({ children }: AuthProps) {
|
export default function AuthComponent({ children }: AuthProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const user = useContext(userContext);
|
const searchParams = useSearchParams()[0];
|
||||||
|
const isLogged = searchParams.has("logged");
|
||||||
async function checkIfCustomerLogged(token: ApiToken) {
|
|
||||||
const isLogged: boolean = await isTokenValid(user.token);
|
|
||||||
|
|
||||||
if (!isLogged) {
|
|
||||||
user.setToken(null);
|
|
||||||
user.setId(null);
|
|
||||||
window.localStorage.removeItem("token");
|
|
||||||
window.localStorage.removeItem("customerId");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user.token)
|
|
||||||
navigate("/login", { replace: true });
|
|
||||||
// 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
|
||||||
checkIfCustomerLogged(user.token);
|
if (!isLogged)
|
||||||
}, [user.token])
|
navigate("/login", { replace: true });
|
||||||
|
}, [isLogged])
|
||||||
|
|
||||||
// by default return the children, the effect will check if the user needs to be redirected
|
if (isLogged)
|
||||||
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.")}/>;
|
||||||
}
|
}
|
@ -8,8 +8,7 @@ type MetadataProps = {
|
|||||||
needsAuth? : boolean
|
needsAuth? : boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MetadataSetter({ children, title, bodyClass, needsAuth = false }: MetadataProps) {
|
export default function MetadataSetter({children, title, bodyClass, needsAuth = false}: MetadataProps){
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title= title;
|
document.title= title;
|
||||||
document.body.classList.add(bodyClass);
|
document.body.classList.add(bodyClass);
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import { fetchCustomerInfo } from "@api";
|
|
||||||
import { ApiToken, CustomerInfo } from "@api/types";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
export function useGetCustomerInfo() {
|
|
||||||
const [customerInfo, setCustomerInfo] = useState<CustomerInfo>({ name: "John Smith", balance: -1 });
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true); // this should be false, but at least the "Loading..." text appears when the page is loading
|
|
||||||
|
|
||||||
const getCustomerInfo = (token: ApiToken, id: number) => {
|
|
||||||
setIsLoading(true);
|
|
||||||
fetchCustomerInfo(token, id)
|
|
||||||
.then(setCustomerInfo)
|
|
||||||
.then(() => setIsLoading(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
return { isLoading, customerInfo, getCustomerInfo };
|
|
||||||
}
|
|
@ -3,7 +3,6 @@ import { createRoot } from "react-dom/client";
|
|||||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||||
import { MetadataSetter, ErrorPage, Login, ForgotPassword, Reset, Transactions } from "@components";
|
import { MetadataSetter, ErrorPage, Login, ForgotPassword, Reset, Transactions } from "@components";
|
||||||
import UserContext from '@ts/userContext'
|
import UserContext from '@ts/userContext'
|
||||||
import { ApiToken } from "@api/types";
|
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@ -42,11 +41,8 @@ const router = createBrowserRouter([
|
|||||||
|
|
||||||
|
|
||||||
const App = function () {
|
const App = function () {
|
||||||
|
const [name, setName] = useState("John Smith")
|
||||||
const [token, setToken] = useState<ApiToken>(window.localStorage.getItem("token"));
|
const userContextValue = { name, setName }
|
||||||
const [id, setId] = useState<number>(Number.parseInt(window.localStorage.getItem("customerId")));
|
|
||||||
const userContextValue = { token, setToken, id, setId }
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserContext.Provider value={userContextValue}>
|
<UserContext.Provider value={userContextValue}>
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
@use "style.scss";
|
@use "style.scss";
|
||||||
@import "variables.module.scss";
|
@import "variables.module.scss";
|
||||||
|
|
||||||
|
|
||||||
body.transactions {
|
body.transactions {
|
||||||
background-image: url("/public/backgroundCurvyLines.png");
|
background-image: url("/public/backgroundCurvyLines.png");
|
||||||
|
|
||||||
@ -40,7 +39,6 @@ body.transactions {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
text-align: right;
|
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
margin-right: .5em;
|
margin-right: .5em;
|
||||||
@ -69,17 +67,6 @@ body.transactions {
|
|||||||
// justify-content: center;
|
// justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.description {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
// overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.amount,
|
|
||||||
.total {
|
|
||||||
min-width: 6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.amount.positive {
|
.amount.positive {
|
||||||
color: $fg-green;
|
color: $fg-green;
|
||||||
}
|
}
|
||||||
@ -92,7 +79,6 @@ body.transactions {
|
|||||||
aside {
|
aside {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
grid-row: 2;
|
grid-row: 2;
|
||||||
min-width: 173px;
|
|
||||||
|
|
||||||
form {
|
form {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -109,69 +95,4 @@ body.transactions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 1044px) {
|
|
||||||
div#tablePage {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
grid-template-rows: 1fr 1fr 10fr 1fr;
|
|
||||||
|
|
||||||
header,
|
|
||||||
main,
|
|
||||||
aside,
|
|
||||||
footer {
|
|
||||||
grid-column: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
grid-row: 1;
|
|
||||||
position: sticky;
|
|
||||||
|
|
||||||
.box.left {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
aside {
|
|
||||||
grid-row: 2;
|
|
||||||
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
form {
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
.inputs-box {
|
|
||||||
// background: red;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
|
|
||||||
.input {
|
|
||||||
// background: blue;
|
|
||||||
width: 10em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
grid-row: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
grid-row: 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,8 +1,6 @@
|
|||||||
import { createContext } from 'react'
|
import { createContext } from 'react'
|
||||||
|
|
||||||
export default createContext({
|
export default createContext({
|
||||||
token : "Unknown",
|
name : "Unknown",
|
||||||
setToken: (newToken: string) => {},
|
setName: (name: string) => {}
|
||||||
id : -1,
|
|
||||||
setId: (newId: number) => {},
|
|
||||||
})
|
})
|
@ -14,8 +14,6 @@
|
|||||||
"@components/*": ["src/components/*"],
|
"@components/*": ["src/components/*"],
|
||||||
"@scss/*": ["src/scss/*"],
|
"@scss/*": ["src/scss/*"],
|
||||||
"@ts/*": ["src/ts/*"],
|
"@ts/*": ["src/ts/*"],
|
||||||
"@api": ["src/api"],
|
|
||||||
"@api/*": ["src/api/*"],
|
|
||||||
"@theme/*": ["themes/onepirate/*"],
|
"@theme/*": ["themes/onepirate/*"],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -25,13 +25,9 @@ module.exports = (env, argv) => {
|
|||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/,
|
test: /\.tsx?/,
|
||||||
loader: 'ts-loader',
|
use: 'ts-loader',
|
||||||
exclude: /node_modules|\.d\.ts$/
|
exclude: /node_modules/
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.d\.ts$/,
|
|
||||||
loader: 'ignore-loader'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.jsx?$/,
|
test: /\.jsx?$/,
|
||||||
|
Loading…
Reference in New Issue
Block a user