Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
48 changes: 48 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "mastertech-code-challenge",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"axios": "^0.21.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-icons": "^4.2.0",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"web-vitals": "^1.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/node": "^15.12.2",
"@types/react": "^17.0.11",
"@types/react-dom": "^17.0.7",
"@types/react-router-dom": "^5.1.7",
"typescript": "^4.3.2"
}
}
14 changes: 14 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap" rel="stylesheet">
<title>Mastertech Code Challenge</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
9 changes: 9 additions & 0 deletions src/@types/IUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type IUser = {
birthday: string;
email: string;
gender: string;
id: number;
name: string;
state: string;
avatar: string;
}
10 changes: 10 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Routes } from './navigation';
import { UserProvider } from './context/UserContext';

const App = () => (
<UserProvider>
<Routes />
</UserProvider>
)

export default App;
15 changes: 15 additions & 0 deletions src/HOC/Auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ElementType } from 'react';
import { Redirect } from 'react-router-dom';
import { verifyAuth } from '../helpers/auth';

export const withAuth = (Component: ElementType, redirectTo = '/login') => {
const Wrapper = (props: unknown) => {
const isAuthorized = verifyAuth();

if (isAuthorized) return <Component {...props} />

return <Redirect to={redirectTo} />
}

return Wrapper;
}
5 changes: 5 additions & 0 deletions src/adapters/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import axios from 'axios';

export const api = axios.create({
baseURL: 'http://jrwee.mocklab.io/'
});
12 changes: 12 additions & 0 deletions src/adapters/login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { api } from './api';

type LoginData = {
email: string;
password: string;
}

export const fetchUserData = async ({ email, password }: LoginData) => {
const { data: user } = await api.post('/user/login', { email, password });

return user;
}
14 changes: 14 additions & 0 deletions src/components/Home/TextIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { IconType } from 'react-icons';
import '../../styles/components/TextIcon.css';

type TextIconProps = {
Icon: IconType;
text: string;
}

export const TextIcon: React.FC<TextIconProps> = ({ Icon, text }) => (
<div className="text_icon__container">
<Icon style={{ marginRight: 8 }} className="text_icon" />
<span className="text_icon">{ text }</span>
</div>
)
13 changes: 13 additions & 0 deletions src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import '../styles/components/Layout.css';

type LayoutProps = {
buttonTitle: string;
buttonAction: () => void;
}

export const Layout: React.FC<LayoutProps> = ({ buttonTitle, buttonAction, children }) => (
<div className="container">
{ children }
<button onClick={buttonAction}>{ buttonTitle }</button>
</div>
);
48 changes: 48 additions & 0 deletions src/context/UserContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useState } from 'react';
import { useEffect } from 'react';
import { createContext } from 'react';
import { IUser } from '../@types/IUser';
import { setItemOnLocalStorage } from '../helpers/localStorage';

type IUserContext = {
user: IUser | null;
login: (userData: IUser, cb: () => void) => void;
logout: (cb: () => void) => void;
}

export const UserContext = createContext({} as IUserContext);

export const UserProvider: React.FC = ({ children }) => {
const [user, setUser] = useState<IUser | null>(null);

useEffect(() => {
getUserFromLocalStorage();
}, [])

const getUserFromLocalStorage = () => {
const stringifiedData = localStorage.getItem('user');
const user = stringifiedData ? JSON.parse(stringifiedData) : null;

setUser(user);
}

const login = (userData: IUser, cb: () => void) => {
setItemOnLocalStorage('user', userData);
setUser(userData);

cb();
}

const logout = (cb: () => void) => {
localStorage.removeItem('user');
setUser(null);

cb();
}

return (
<UserContext.Provider value={{ user, login, logout }}>
{ children }
</UserContext.Provider>
)
}
7 changes: 7 additions & 0 deletions src/helpers/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { getItemFromLocalStorage } from './localStorage';

export const verifyAuth = () => {
const user = getItemFromLocalStorage('user', null);

return !!user;
}
5 changes: 5 additions & 0 deletions src/helpers/date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const formatDate = (date: string) => {
const newDate = date.split('-').reverse().join('/');

return newDate;
}
11 changes: 11 additions & 0 deletions src/helpers/localStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const getItemFromLocalStorage = (key: string, defaultValue: unknown = null) => {
const stringifiedData = localStorage.getItem(key);
const data = stringifiedData ? JSON.parse(stringifiedData) : defaultValue;

return data;
}

export const setItemOnLocalStorage = (key: string, data: unknown) => {
const stringfiedData = JSON.stringify(data);
localStorage.setItem(key, stringfiedData);
}
11 changes: 11 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './styles/globalStyles.css';

ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
14 changes: 14 additions & 0 deletions src/navigation/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Home from '../pages/Home';
import { Login } from '../pages/Login';

export const Routes = () => (
<div style={{ width: '100vw', height: '100vh', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
</Switch>
</Router>
</div>
);
28 changes: 28 additions & 0 deletions src/pages/Home/HomeUI.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { IUser } from '../../@types/IUser'
import { Layout } from '../../components/Layout';
import { FaEnvelope, FaCalendar, FaMapPin, FaVenusMars } from 'react-icons/fa';
import '../../styles/pages/Home.css';
import { TextIcon } from '../../components/Home/TextIcon';
import { formatDate } from '../../helpers/date';

type HomeProps = {
user: IUser;
logout: () => void;
}

export const HomeUI: React.FC<HomeProps> = ({ user, logout }) => (
<Layout buttonTitle="Log out" buttonAction={logout}>
<div className="home_container">
<div className="user_info_container">
<h1>{ user.name }</h1>

<TextIcon Icon={FaEnvelope} text={user.email} />
<TextIcon Icon={FaCalendar} text={formatDate(user.birthday)} />
<TextIcon Icon={FaMapPin} text={user.state} />
<TextIcon Icon={FaVenusMars} text={user.gender} />
</div>

<img src={user.avatar} alt={`${user.name}`} />
</div>
</Layout>
);
21 changes: 21 additions & 0 deletions src/pages/Home/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useContext } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { UserContext } from '../../context/UserContext';
import { withAuth } from '../../HOC/Auth';
import { HomeUI } from './HomeUI';

const Home = ({ history }: RouteComponentProps) => {
const { user, logout } = useContext(UserContext);

const handleLogout = () => {
logout(() => {
history.replace('/login')
});
}

if (!user) return <h1>Usuário não encontrado</h1>

return <HomeUI user={user} logout={handleLogout} />
}

export default withAuth(Home);
45 changes: 45 additions & 0 deletions src/pages/Login/LoginUI.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import '../../styles/pages/Login.css';
import { Layout } from '../../components/Layout';

type LoginData = {
email: string;
password: string
}

interface LoginProps {
error: string;
data: LoginData
handleLogin: () => void;
handleInput: (event: any) => void;
}

export const LoginUI: React.FC<LoginProps> = ({ error, data, handleLogin, handleInput }) => (
<Layout buttonTitle="Login" buttonAction={handleLogin}>
<h1>Login</h1>

<form>
<label>
<span>E-mail</span>
<input
type="email"
name="email"
placeholder="ipsum@lorem.com"
value={data.email}
onChange={handleInput}
/>
</label>
<label>
<span>Senha</span>
<input
type="password"
name="password"
placeholder="*************"
value={data.password}
onChange={handleInput}
/>
</label>

<span className="error">{ error }</span>
</form>
</Layout>
);
Loading