Skip to content

Commit

Permalink
feat(login): add hook for login + interceptor
Browse files Browse the repository at this point in the history
  • Loading branch information
ZiyedB committed Feb 14, 2021
1 parent 6ea4b73 commit 2ecb370
Show file tree
Hide file tree
Showing 20 changed files with 340 additions and 27 deletions.
3 changes: 3 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
extends: ['airbnb-typescript-prettier'],
ignorePatterns: ['.eslintrc.js'],
// Rules can be here to override the preset of eslint from airbnb, if they are too strict.
rules: {
'@typescript-eslint/ban-ts-comment': 'off',
Expand All @@ -22,6 +23,8 @@ module.exports = {
'no-console': 'off',

'react/destructuring-assignment': 0,
'react/jsx-props-no-spreading': 'off',
'import/prefer-default-export': 0,
// 'react/prop-types': 0, -> this is an example
},
overrides: [
Expand Down
8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"axios": "^0.21.1",
"brace": "^0.11.1",
"jsoneditor": "^9.1.9",
"jsoneditor-react": "^3.1.0",
Expand Down
52 changes: 38 additions & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import React, { useState } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import './App.scss';
import { storageNames } from './config';
import UserContext from './hooks/UserContext';
import Ipfs from './ipfs/Ipfs';
import IpfsList from './ipfs/IpfsList';
import BaseLayout from './layouts/BaseLayout';
import Login from './pages/Login/Login';
import { ProtectedRoute } from './shared/ProtectedRoute';

import "./App.scss";
import BaseLayout from "./layouts/BaseLayout";
import IpfsList from "./ipfs/IpfsList";
import Ipfs from "./ipfs/Ipfs";
function App(): JSX.Element {
// Handle user state
// Ask if we should have this instead directly in a utils instead
const [isLoggedIn, setLoggedIn] = useState(false);

function App() {
// Handle user login by setting the storage and state
const login = (token: string): void => {
setLoggedIn(true);
localStorage.setItem(storageNames.user, token);
};

// Handle user logout
const logout = (): void => {
setLoggedIn(false);
localStorage.removeItem(storageNames.user);
};

// TODO: still need to handle the first time getting to site ( if token still valid, present etc. )
return (
<Router>
<BaseLayout>
<Switch>
<Route path="/ipfs/:hash" component={Ipfs} />
<Route path={["/", "/ipfs"]} component={IpfsList} />
</Switch>
</BaseLayout>
</Router>
<UserContext.Provider value={{ isLoggedIn, login, logout }}>
<Router>
<BaseLayout>
<Switch>
<Route path="/login" component={Login} />
<ProtectedRoute path="/ipfs/:hash" component={Ipfs} />
<ProtectedRoute path={['/', '/ipfs']} component={IpfsList} />
</Switch>
</BaseLayout>
</Router>
</UserContext.Provider>
);
}

Expand Down
28 changes: 19 additions & 9 deletions src/NavigationBar/NavigationBar.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import React from 'react';
import React, { useContext } from 'react';
import { FaUserCircle } from 'react-icons/fa';
import { Link } from 'react-router-dom';

import UserContext from '../hooks/UserContext';
import './NavigationBar.scss';

export const NavigationBar = (): JSX.Element => {
const NavigationBar = (): JSX.Element => {
const userContext = useContext(UserContext);

const logout = (): void => {
userContext.logout();
};

return (
<div className="navigation-bar">
<nav className="navigation-bar__nav">
Expand All @@ -23,12 +29,16 @@ export const NavigationBar = (): JSX.Element => {
</ul>
</div>
</div>
<div className="navigation-bar__user">
{/* TODO: Need to be a user page */}
<a className="navigation-bar__link navigation-bar__user-link" href="/">
<FaUserCircle />
</a>
</div>
{userContext.isLoggedIn ? (
<div className="navigation-bar__user">
{/* TODO: Need to have this as a small menu showing up */}
<a className="navigation-bar__link navigation-bar__user-link" onClick={logout} href="/">
<FaUserCircle />
</a>
</div>
) : (
''
)}
</nav>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/NavigationBar/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './NavigationBar';
export { default as NavigationBar } from './NavigationBar';
5 changes: 5 additions & 0 deletions src/config/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const apis = {
// TODO: Replace this by the BE api urls we would be using. Probably storing this in a config file
url: 'https://jsonplaceholder.typicode.com',
timeout: 2 * 60 * 1000, // 2 minutes timeout
};
2 changes: 2 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { apis } from './api';
export { storageNames } from './storage-names';
3 changes: 3 additions & 0 deletions src/config/storage-names.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const storageNames = {
user: 'pl11',
};
11 changes: 11 additions & 0 deletions src/hooks/UserContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createContext } from 'react';

interface UserContextState {
isLoggedIn: boolean;
login: (token: string) => void;
logout: () => void;
}

const UserContext = createContext({} as UserContextState);

export default UserContext;
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as UserContext } from './UserContext';
2 changes: 1 addition & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import "./index.scss";
import './index.scss';
import App from './App';
import reportWebVitals from './reportWebVitals';
import 'semantic-ui-css/semantic.min.css';
Expand Down
4 changes: 2 additions & 2 deletions src/layouts/BaseLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from 'react';
import { Container } from 'semantic-ui-react';
import React, { ReactNode } from 'react';
import { NavigationBar } from '../NavigationBar';

interface BaseLayoutProps {
children: any;
}

function BaseLayout(props: BaseLayoutProps): ReactNode {
function BaseLayout(props: BaseLayoutProps): JSX.Element {
return (
<div>
<NavigationBar />
Expand Down
72 changes: 72 additions & 0 deletions src/pages/Login/Login.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
$elevation-card: 0 0 1px 0 rgba(8, 11, 14, 0.6), 0 16px 16px -1px rgba(8, 11, 14, 0.1);
$color-secondary: #e1e4e8;

.login {
&__card {
max-width: 400px;
margin: auto;
box-shadow: $elevation-card;
padding: 30px;
background: white;
}

&__title {
font-size: 22px;
line-height: 32px;
font-weight: 500;
text-align: center;
margin: 8px 0 20px;
font-weight: bold;
}

&__input-container {
position: relative;
margin-top: 20px;
}

&__input {
border-radius: 100px;
border: 1px solid $color-secondary;
font-size: 14px;
line-height: 24px;
font-weight: 500;
height: 40px;
width: 100%;
outline: none;
padding: 0 15px;
}

&__input-icon {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
background: transparent;
border: none;
outline: none;
display: inline-block;
padding: 10px;
color: $color-secondary;
}

&__password-icon {
color: black;
cursor: pointer;
}

&__button {
border-radius: 100px;
font-size: 14px;
line-height: 24px;
background: black;
color: white;
padding: 0 10px;
height: 40px;
width: 100%;
border: none;
margin-top: 40px;
cursor: pointer;
padding: 0 20px;
outline: none;
}
}
108 changes: 108 additions & 0 deletions src/pages/Login/Login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React, { useContext } from 'react';
import { FaEnvelope, FaEye, FaEyeSlash } from 'react-icons/fa';
import { Redirect, RouteComponentProps } from 'react-router-dom';
import UserContext from '../../hooks/UserContext';
import paralinkApi from '../../services/interceptor';
import './Login.scss';

interface LoginProps {
from: string;
}

const Login = (props: RouteComponentProps<{}, {}, LoginProps>): JSX.Element => {
const userContext = useContext(UserContext);

const [state, setState] = React.useState({
email: '',
password: '',
seePassword: false,
});

const { from } = props.location.state || { from: { pathname: '/' } };

const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
const { id, value } = e.target;
setState((prevState) => ({
...prevState,
[id]: value,
}));
};

const togglePassword = (): void => {
setState((prevState) => ({
...prevState,
seePassword: !prevState.seePassword,
}));
};

const login = async (): Promise<any> => {
// TODO: put like a loading icon on the login button

// TODO: do like a safe check on the email & password here

// Just to check
await paralinkApi
// For now this is going to be the placeholder
.get('https://jsonplaceholder.typicode.com/todos')
.then(() => {
// We should get the login token here from the BE
const token = 'faketokenhere';
userContext.login(token);
})
.catch((err: any) => {
// TODO: Need to display the error in the UI ( user not found, not correct password, not correct email)
console.error(err);
})
.finally(() => {
// TODO: login loading should be set to false here
});
};

if (userContext.isLoggedIn) {
return <Redirect to={from} />;
}

// TODO: handle pressing enter to submit ( maybe having it as a form instead and using submit )
return (
<div className="login__card">
<div className="login__title">Login into your account</div>
<div className="login__input-container">
<input
type="email"
className="login__email login__input"
id="email"
aria-describedby="emailHelp"
placeholder="[email protected]"
value={state.email}
onChange={handleChange}
/>
<div className="login__input-icon">
<FaEnvelope className="login__email-icon" />
</div>
</div>
<div className="login__input-container">
<input
type={state.seePassword ? 'text' : 'password'}
className="login__password login__input"
id="password"
placeholder="Password"
value={state.password}
onChange={handleChange}
/>

<button className="login__input-icon" type="button" onClick={togglePassword}>
{state.seePassword ? (
<FaEyeSlash className="login__password-icon" />
) : (
<FaEye className="login__password-icon" />
)}
</button>
</div>
<button className="login__button" type="submit" onClick={login}>
Login
</button>
</div>
);
};

export default Login;
1 change: 1 addition & 0 deletions src/pages/Login/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Login } from './Login';
Loading

0 comments on commit 2ecb370

Please sign in to comment.