diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..e69de29 diff --git a/.eslintrc.js b/.eslintrc.js index a8e8325..6bad519 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,6 +3,8 @@ module.exports = { ignorePatterns: ['.eslintrc.js'], // Rules can be here to override the preset of eslint from airbnb, if they are too strict. rules: { + camelcase: 'warn', + '@typescript-eslint/no-empty-function': 'warn', '@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/explicit-function-return-type': 'off', diff --git a/package-lock.json b/package-lock.json index 5f4b849..e020b20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7656,9 +7656,9 @@ "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" }, "immer": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/immer/-/immer-7.0.9.tgz", - "integrity": "sha512-Vs/gxoM4DqNAYR7pugIxi0Xc8XAun/uy7AQu4fLLqaTBHxjOP9pJ266Q9MWA/ly4z6rAFZbvViOtihxUZ7O28A==" + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz", + "integrity": "sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==" }, "import-cwd": { "version": "2.1.0", @@ -10608,9 +10608,9 @@ } }, "open": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.0.tgz", - "integrity": "sha512-PGoBCX/lclIWlpS/R2PQuIR4NJoXh6X5AwVzE7WXnWRGvHg7+4TBCgsujUgiPpm0K1y4qvQeWnCWVTpTKZBtvA==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "requires": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -12501,9 +12501,9 @@ } }, "react-dev-utils": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.1.tgz", - "integrity": "sha512-rlgpCupaW6qQqvu0hvv2FDv40QG427fjghV56XyPcP5aKtOAPzNAhQ7bHqk1YdS2vpW1W7aSV3JobedxuPlBAA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.3.tgz", + "integrity": "sha512-4lEA5gF4OHrcJLMUV1t+4XbNDiJbsAWCH5Z2uqlTqW6dD7Cf5nEASkeXrCI/Mz83sI2o527oBIFKVMXtRf1Vtg==", "requires": { "@babel/code-frame": "7.10.4", "address": "1.1.2", @@ -12518,13 +12518,13 @@ "global-modules": "2.0.0", "globby": "11.0.1", "gzip-size": "5.1.1", - "immer": "7.0.9", + "immer": "8.0.1", "is-root": "2.1.0", "loader-utils": "2.0.0", "open": "^7.0.2", "pkg-up": "3.1.0", "prompts": "2.4.0", - "react-error-overlay": "^6.0.8", + "react-error-overlay": "^6.0.9", "recursive-readdir": "2.2.2", "shell-quote": "1.7.2", "strip-ansi": "6.0.0", @@ -12642,9 +12642,9 @@ } }, "react-error-overlay": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz", - "integrity": "sha512-HvPuUQnLp5H7TouGq3kzBeioJmXms1wHy9EGjz2OURWBp4qZO6AfGEcnxts1D/CbwPLRAgTMPCEgYhA3sEM4vw==" + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", + "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==" }, "react-fast-compare": { "version": "3.2.0", @@ -15355,9 +15355,9 @@ } }, "typescript": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", - "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==" + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.2.tgz", + "integrity": "sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ==" }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", diff --git a/package.json b/package.json index d0bd862..09b83f1 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "react-scripts": "4.0.1", "semantic-ui-css": "^2.4.1", "semantic-ui-react": "^2.0.3", - "typescript": "^4.1.3", + "typescript": "^4.2.2", "web-vitals": "^0.2.4", "yup": "^0.32.8" }, @@ -75,7 +75,7 @@ ] }, "lint-staged": { - "*.{js,tsx}": "eslint --fix" + "*.{js,ts,tsx}": "eslint --fix" }, "husky": { "hooks": { diff --git a/src/App.tsx b/src/App.tsx index 89ad187..60383bb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,39 +1,28 @@ import React, { useState } from 'react'; -import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; -import './App.scss'; +import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom'; import BaseLayout from './components/layouts/BaseLayout'; -import { storageNames } from './config'; -import UserContext from './hooks/UserContext'; -import Ipfs from './pages/ipfs/Ipfs'; -import IpfsList from './pages/ipfs/IpfsList'; -import Login from './pages/Login/Login'; -import { ProtectedRoute } from './shared/ProtectedRoute'; +import Login from './components/pages/auth/Login'; +import Page from './components/Page'; +import { ERROR_404_PAGE, LOGIN_PAGE, USER_PAGES } from './components/urls'; +import Error404 from './components/pages/errors/Error404'; +import UserContext, { emptyUser } from './state/user'; +import './App.scss'; function App(): JSX.Element { - // Handle user state - const accessToken = localStorage.getItem(storageNames.user) || ''; - const [isLoggedIn, setLoggedIn] = useState(!!accessToken); - - // Handle user login by setting the storage and state - const login = (token: string): void => { - setLoggedIn(true); - localStorage.setItem(storageNames.user, token); - }; + const [user, setUser] = useState({ ...emptyUser }); - // Handle user logout - const logout = (): void => { - setLoggedIn(false); - localStorage.removeItem(storageNames.user); - }; + const login = (email: string): void => setUser({ email, isLoggedIn: true }); + const logout = (): void => setUser({ ...emptyUser }); return ( - + - - - + + + + } /> diff --git a/src/api/api.ts b/src/api/api.ts new file mode 100644 index 0000000..98561c4 --- /dev/null +++ b/src/api/api.ts @@ -0,0 +1,70 @@ +import axios from 'axios'; + +const OBTAIN_TOKEN = '/token/obtain/'; +// const REFRESH_TOKEN_URL = '/token/refresh/'; + +export const ACCESS_TOKEN = 'access_token'; +export const REFRESH_TOKEN = 'refresh_token'; + +interface JWTTokenResponse { + refresh: string; + access: string; +} + +export const axiosInstance = axios.create({ + baseURL: 'http://127.0.0.1:7424/api/', + timeout: 5000, + headers: { + // 'Authorization': localStorage.getItem('access_token') ? 'JWT ' + localStorage.getItem('access_token') : null, + 'Content-Type': 'application/json', + accept: 'application/json', + }, +}); + +export const saveTokens = (jwtToken: JWTTokenResponse): JWTTokenResponse => { + localStorage.setItem(ACCESS_TOKEN, jwtToken.access); + localStorage.setItem(REFRESH_TOKEN, jwtToken.refresh); + return jwtToken; +}; + +const localGet = (key: string, defaultValue = ''): string => { + const value = localStorage.getItem(key); + if (value === null) return defaultValue; + return value; +}; + +export const getAccessToken = (): string => localGet(ACCESS_TOKEN); +export const getRefreshToken = (): string => localGet(REFRESH_TOKEN); + +axiosInstance.interceptors.response.use( + (res) => res, + (err) => { + // const originalRequest = err.config; + // If refresh tokens is expired redirect to login page + // if (err.response.status === 401 && originalRequest.url === REFRESH_TOKEN_URL) { + // window.location.href = '/login/'; + // return Promise.reject(err); + // } + + // // If access token is expired update it + // if (err.response.status === 401 && err.response.statusText === 'Unauthorized') { + // return axiosInstance + // .post(REFRESH_TOKEN_URL, {refresh: localStorage.getItem(REFRESH_TOKEN)}) + // .then(res => res.data) + // .then(saveTokens) + // .then(res => { + // axiosInstance.defaults.headers['Authorization'] = 'JWT ' + res.access; + // originalRequest.headers['Authorization'] = 'JWT ' + res.access; + + // return axiosInstance(originalRequest); + // }) + // } + + return Promise.reject(err); + }, +); + +export const obtainTokenApi = async (email: string, password: string): Promise => + axiosInstance + .post(OBTAIN_TOKEN, { email, password }) + .then((res) => res.data); diff --git a/src/api/auth/auth.ts b/src/api/auth/auth.ts new file mode 100644 index 0000000..7c4cd48 --- /dev/null +++ b/src/api/auth/auth.ts @@ -0,0 +1,24 @@ +// import { axiosInstance, obtainTokenApi, saveTokens } from "../api" + +// const LOGIN_URL = ''; + +export interface UserAuthenticationData { + email: string; + password: string; +} + +const loginUser = (data: UserAuthenticationData): Promise => { + const { email, password } = data; + + if (email !== 'test@test.com') throw new Error('Email does not exist!'); + if (password !== 'geslo123') throw new Error('Password is incorrect!'); + // axiosInstance.post<{}>(LOGIN_URL, data) + // .then((res) => res.data); + return Promise.resolve(); +}; + +export const authenticateUser = async (data: UserAuthenticationData): Promise => { + await loginUser(data); + // const jwtTokens = await obtainTokenApi(data.email, data.password); + // saveTokens(jwtTokens); +}; diff --git a/src/components/Page.tsx b/src/components/Page.tsx new file mode 100644 index 0000000..b518d41 --- /dev/null +++ b/src/components/Page.tsx @@ -0,0 +1,21 @@ +import React, { useContext } from 'react'; +import { Redirect, Route, Switch } from 'react-router-dom'; +import Ipfs from '../pages/ipfs/Ipfs'; +import IpfsList from '../pages/ipfs/IpfsList'; +import UserContext from '../state/user'; +import { IPFS_BASE_PAGE, IPFS_PAGE, LOGIN_PAGE } from './urls'; + +const Page = (): JSX.Element => { + const { user } = useContext(UserContext); + + if (!user.isLoggedIn) return ; + + return ( + + + + + ); +}; + +export default Page; diff --git a/src/components/layouts/BaseLayout/BaseLayout.tsx b/src/components/layouts/BaseLayout/BaseLayout.tsx index 2cdf024..d36ee55 100644 --- a/src/components/layouts/BaseLayout/BaseLayout.tsx +++ b/src/components/layouts/BaseLayout/BaseLayout.tsx @@ -2,18 +2,14 @@ import React from 'react'; import { Container } from 'semantic-ui-react'; import { NavigationBar } from '../NavigationBar'; -interface BaseLayoutProps { - children: any; -} +const BaseLayout: React.FC<{}> = ({ children }) => ( +
+ -export default function BaseLayout(props: BaseLayoutProps): JSX.Element { - return ( -
- + + {children} + +
+); - - {props.children} - -
- ); -} +export default BaseLayout; diff --git a/src/components/layouts/NavigationBar/NavigationBar.tsx b/src/components/layouts/NavigationBar/NavigationBar.tsx index daf082b..f967549 100644 --- a/src/components/layouts/NavigationBar/NavigationBar.tsx +++ b/src/components/layouts/NavigationBar/NavigationBar.tsx @@ -1,35 +1,32 @@ import React, { useContext } from 'react'; import { FaUserCircle } from 'react-icons/fa'; import { Link } from 'react-router-dom'; -import UserContext from '../../../hooks/UserContext'; +import UserContext from '../../../state/user'; +import { HOME_PAGE, IPFS_BASE_PAGE } from '../../urls'; import './NavigationBar.scss'; const NavigationBar = (): JSX.Element => { - const userContext = useContext(UserContext); - - const logout = (): void => { - userContext.logout(); - }; + const { user, logout } = useContext(UserContext); return (