├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
└── src
├── App.js
├── assets
└── airbnb-logo.svg
├── index.js
├── pages
├── AddProperty
│ ├── index.js
│ └── styles.js
├── App
│ ├── components
│ │ ├── Button
│ │ │ ├── index.js
│ │ │ └── styles.js
│ │ └── Properties
│ │ │ ├── index.js
│ │ │ └── styles.js
│ ├── index.js
│ └── styles.js
├── Property
│ ├── index.js
│ └── styles.js
├── SignIn
│ ├── index.js
│ └── styles.js
└── SignUp
│ ├── index.js
│ └── styles.js
├── routes.js
├── services
├── api.js
└── auth.js
└── styles
└── global.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-content/blog-adonis-reactjs-react-native-airbnb-web/66dda7042f3cc9db343df6ae7a3f48b832064e24/README.md
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "airbnb-web",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "axios": "^0.18.0",
7 | "classnames": "^2.2.6",
8 | "font-awesome": "^4.7.0",
9 | "polished": "^1.9.3",
10 | "prop-types": "^15.6.2",
11 | "react": "^16.4.2",
12 | "react-dimensions": "^1.3.1",
13 | "react-dom": "^16.4.2",
14 | "react-dropzone": "^5.0.1",
15 | "react-map-gl": "^3.3.4",
16 | "react-router-dom": "^4.3.1",
17 | "react-router-modal": "^1.4.1",
18 | "react-scripts": "1.1.4",
19 | "stringquery": "^1.0.8",
20 | "styled-components": "^3.4.2"
21 | },
22 | "scripts": {
23 | "start": "react-scripts start",
24 | "build": "react-scripts build",
25 | "test": "react-scripts test --env=jsdom",
26 | "eject": "react-scripts eject"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketseat-content/blog-adonis-reactjs-react-native-airbnb-web/66dda7042f3cc9db343df6ae7a3f48b832064e24/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Routes from "./routes";
3 | import "./styles/global";
4 |
5 | const App = () => ;
6 | export default App;
7 |
--------------------------------------------------------------------------------
/src/assets/airbnb-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(, document.getElementById('root'));
--------------------------------------------------------------------------------
/src/pages/AddProperty/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { withRouter } from "react-router-dom";
3 | import querySearch from "stringquery";
4 | import classNames from "classnames";
5 | import PropTypes from "prop-types";
6 |
7 | import { Form, File } from "./styles";
8 |
9 | import api from "../../services/api";
10 |
11 | class AddProperty extends Component {
12 | static propTypes = {
13 | location: PropTypes.shape({
14 | search: PropTypes.string
15 | }).isRequired,
16 | history: PropTypes.shape({
17 | push: PropTypes.func
18 | }).isRequired
19 | };
20 |
21 | state = {
22 | title: "",
23 | address: "",
24 | price: "",
25 | error: "",
26 | files: []
27 | };
28 |
29 | componentDidMount() {
30 | const params = querySearch(this.props.location.search);
31 | if (
32 | !params.hasOwnProperty("latitude") ||
33 | !params.hasOwnProperty("longitude")
34 | ) {
35 | alert("É necessário definir a latitude e longitude para um imóvel.");
36 | this.props.history.push("/app");
37 | }
38 |
39 | this.setState({ ...params });
40 | }
41 |
42 | handleDrop = files => this.setState({ files });
43 |
44 | renderFiles() {
45 | const { files } = this.state;
46 | return !files.length ? (
47 | Jogue as imagens ou clique aqui para adiciona-las
48 | ) : (
49 | files.map(file =>
)
50 | );
51 | }
52 |
53 | handleSubmit = async e => {
54 | e.preventDefault();
55 |
56 | try {
57 | const { title, address, price, latitude, longitude, files } = this.state;
58 |
59 | if (!title || !address || !price || !latitude || !longitude) {
60 | this.setState({ error: "Preencha todos os campos" });
61 | return;
62 | }
63 |
64 | const {
65 | data: { id }
66 | } = await api.post("/properties", {
67 | title,
68 | address,
69 | price,
70 | latitude,
71 | longitude
72 | });
73 |
74 | if (!files.length) this.props.history.push("/app");
75 |
76 | const data = new FormData();
77 | files.map((file, index) =>
78 | data.append(`image[${index}]`, file, file.name)
79 | );
80 |
81 | const config = {
82 | headers: {
83 | "content-type": "multipart/form-data"
84 | }
85 | };
86 |
87 | await api.post(`/properties/${id}/images`, data, config);
88 |
89 | this.props.history.push("/app");
90 | } catch (err) {
91 | this.setState({ error: "Ocorreu algum erro ao adicionar o imóvel" });
92 | }
93 | };
94 |
95 | handleCancel = e => {
96 | e.preventDefault();
97 |
98 | this.props.history.push("/app");
99 | };
100 |
101 | render() {
102 | return (
103 |
136 | );
137 | }
138 | }
139 |
140 | export default withRouter(AddProperty);
141 |
--------------------------------------------------------------------------------
/src/pages/AddProperty/styles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import Dropzone from "react-dropzone";
3 |
4 | export const File = styled(Dropzone)`
5 | border: 2px dashed #ff3333;
6 | width: 100%;
7 | max-width: 660px;
8 | font-size: 16px;
9 | color: #777777;
10 | text-align: center;
11 | display: grid;
12 | grid-template-columns: 100px 100px 100px;
13 | grid-gap: 5px;
14 | background-color: #fff;
15 | color: #444;
16 | &.without-files {
17 | display: flex;
18 | }
19 | img {
20 | width: 100px;
21 | }
22 | p {
23 | margin-top: 15px;
24 | border: none !important;
25 | }
26 | `;
27 |
28 | export const Form = styled.form`
29 | display: flex;
30 | flex-direction: column;
31 | align-items: center;
32 | padding: 10px 20px;
33 | p {
34 | color: #ff3333;
35 | margin-bottom: 15px;
36 | border: 1px solid #ff3333;
37 | padding: 10px;
38 | width: 100%;
39 | text-align: center;
40 | }
41 | input {
42 | flex: 1;
43 | height: 46px;
44 | margin-bottom: 15px;
45 | padding: 0 20px;
46 | color: #777777;
47 | font-size: 15px;
48 | width: 100%;
49 | border: 1px solid #cccccc;
50 | &::placeholder {
51 | color: #999999;
52 | }
53 | }
54 | hr {
55 | margin: 20px 0;
56 | border: none;
57 | border-bottom: 1px solid #cdcdcd;
58 | width: 100%;
59 | }
60 | a {
61 | font-size: 16px;
62 | font-weight: bold;
63 | color: #999999;
64 | text-decoration: none;
65 | }
66 | div.actions {
67 | display: flex;
68 | margin-top: 15px;
69 | width: 100%;
70 | justify-content: space-between;
71 | button {
72 | color: #ffffff;
73 | font-size: 16px;
74 | background: #fc6963;
75 | height: 56px;
76 | border: 0;
77 | border-radius: 5px;
78 | padding: 0 30px;
79 | &.cancel {
80 | background: #222222;
81 | }
82 | }
83 | }
84 | `;
85 |
--------------------------------------------------------------------------------
/src/pages/App/components/Button/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import { Button } from "./styles";
5 |
6 | const CustomButton = ({ children, color, ...props }) => (
7 |
10 | );
11 |
12 | CustomButton.propTypes = {
13 | children: PropTypes.element.isRequired,
14 | color: PropTypes.string.isRequired,
15 | props: PropTypes.object
16 | };
17 |
18 | export default CustomButton;
19 |
--------------------------------------------------------------------------------
/src/pages/App/components/Button/styles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { darken } from "polished";
3 |
4 | export const Button = styled.button`
5 | width: 60px;
6 | height: 60px;
7 | border-radius: 60px;
8 | border: none;
9 | background-color: ${({ color }) => color};
10 | margin-top: 10px;
11 | color: #fff;
12 | i {
13 | font-size: 18px;
14 | }
15 | &:hover {
16 | background-color: ${({ color }) => darken(0.05, color)};
17 | }
18 | &:active {
19 | background-color: ${({ color }) => darken(0.07, color)};
20 | }
21 | `;
22 |
--------------------------------------------------------------------------------
/src/pages/App/components/Properties/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Marker } from "react-map-gl";
3 | import { Link } from "react-router-dom";
4 | import PropTypes from "prop-types";
5 |
6 | import { Pin } from "./styles";
7 |
8 | const intlMonetary = new Intl.NumberFormat("pt-BR", {
9 | style: "currency",
10 | currency: "BRL",
11 | minimumFractionDigits: 2
12 | });
13 |
14 | const Properties = ({ properties, match }) =>
15 | properties.map(property => (
16 |
21 |
22 |
23 | {intlMonetary.format(property.price)}
24 |
25 |
26 |
27 | ));
28 |
29 | Properties.propTypes = {
30 | properties: PropTypes.arrayOf(
31 | PropTypes.shape({
32 | id: PropTypes.number,
33 | title: PropTypes.string,
34 | price: PropTypes.number,
35 | longitude: PropTypes.number,
36 | latitude: PropTypes.number
37 | })
38 | ).isRequired,
39 | match: PropTypes.shape({
40 | url: PropTypes.string
41 | }).isRequired
42 | };
43 |
44 | export default Properties;
45 |
--------------------------------------------------------------------------------
/src/pages/App/components/Properties/styles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | export const Pin = styled.div`
3 | padding: 5px;
4 | background: #fc6963;
5 | border: 0;
6 | border-radius: 5px;
7 | a {
8 | color: #fff;
9 | font-size: 14px;
10 | text-decoration: none;
11 | }
12 | `;
13 |
--------------------------------------------------------------------------------
/src/pages/App/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from "react";
2 | import Dimensions from "react-dimensions";
3 | import { withRouter } from "react-router-dom";
4 | import { ModalRoute } from "react-router-modal";
5 | import MapGL from "react-map-gl";
6 | import PropTypes from "prop-types";
7 | import debounce from "lodash/debounce";
8 |
9 | import api from "../../services/api";
10 | import { logout } from "../../services/auth";
11 |
12 | import Properties from "./components/Properties";
13 | import Button from "./components/Button";
14 |
15 | import AddProperty from "../AddProperty";
16 | import Property from "../Property";
17 |
18 | import { Container, ButtonContainer, PointReference } from "./styles";
19 |
20 | const TOKEN =
21 | "pk.eyJ1IjoiaGlnb3JvY2tldCIsImEiOiJjamlrdWJuY3gyaHYxM3Bvbmg0cGRwY3R0In0._TdjX9rYrjZ6Q6FFXOGwsQ";
22 |
23 | class Map extends Component {
24 | static propTypes = {
25 | containerWidth: PropTypes.number.isRequired,
26 | containerHeight: PropTypes.number.isRequired
27 | };
28 |
29 | constructor() {
30 | super();
31 | this.updatePropertiesLocalization = debounce(
32 | this.updatePropertiesLocalization,
33 | 500
34 | );
35 | }
36 |
37 | state = {
38 | viewport: {
39 | latitude: -27.2108001,
40 | longitude: -49.6446024,
41 | zoom: 12.8,
42 | bearing: 0,
43 | pitch: 0
44 | },
45 | properties: [],
46 | addActivate: false
47 | };
48 |
49 | componentDidMount() {
50 | this.loadProperties();
51 | }
52 |
53 | updatePropertiesLocalization() {
54 | this.loadProperties();
55 | }
56 |
57 | loadProperties = async () => {
58 | const { latitude, longitude } = this.state.viewport;
59 | try {
60 | const response = await api.get("/properties", {
61 | params: { latitude, longitude }
62 | });
63 | this.setState({ properties: response.data });
64 | } catch (err) {
65 | console.log(err);
66 | }
67 | };
68 |
69 | handleLogout = e => {
70 | logout();
71 | this.props.history.push("/");
72 | };
73 |
74 | handleAddProperty = () => {
75 | const { match, history } = this.props;
76 | const { latitude, longitude } = this.state.viewport;
77 | history.push(
78 | `${match.url}/properties/add?latitude=${latitude}&longitude=${longitude}`
79 | );
80 |
81 | this.setState({ addActivate: false });
82 | };
83 |
84 | renderActions() {
85 | return (
86 |
87 |
93 |
96 |
97 | );
98 | }
99 |
100 | renderButtonAdd() {
101 | return (
102 | this.state.addActivate && (
103 |
104 |
105 |
106 |
109 |
115 |
116 |
117 | )
118 | );
119 | }
120 |
121 | render() {
122 | const {
123 | containerWidth: width,
124 | containerHeight: height,
125 | match
126 | } = this.props;
127 | const { properties, addActivate } = this.state;
128 | return (
129 |
130 | this.setState({ viewport })}
137 | onViewStateChange={this.updatePropertiesLocalization.bind(this)}
138 | >
139 | {!addActivate && }
140 |
141 | {this.renderButtonAdd()}
142 | {this.renderActions()}
143 |
148 |
153 |
154 | );
155 | }
156 | }
157 |
158 | const DimensionedMap = withRouter(Dimensions()(Map));
159 | const App = () => (
160 |
161 |
162 |
163 | );
164 |
165 | export default App;
166 |
--------------------------------------------------------------------------------
/src/pages/App/styles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { darken } from "polished";
3 |
4 | export const Container = styled.div`
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | height: 100vh;
9 | `;
10 |
11 | export const ButtonContainer = styled.div`
12 | position: absolute;
13 | bottom: 20px;
14 | right: 10px;
15 | display: flex;
16 | flex-direction: column;
17 | `;
18 |
19 | export const PointReference = styled.div`
20 | position: absolute;
21 | top: 0;
22 | width: 100vw;
23 | height: 100vh;
24 | pointer-events: none;
25 | display: flex;
26 | align-items: center;
27 | justify-content: center;
28 | flex-direction: column;
29 | i {
30 | color: #fc6963;
31 | pointer-events: all;
32 | font-size: 50px;
33 | margin-top: 112px;
34 | margin-left: 12px;
35 | -webkit-text-fill-color: #fc6963;
36 | -webkit-text-stroke-width: 2px;
37 | -webkit-text-stroke-color: ${() => darken(0.05, "#fc6963")};
38 | }
39 | div {
40 | margin-top: 100px;
41 | button {
42 | border: none;
43 | font-size: 15px;
44 | height: 46px;
45 | margin: 0 10px;
46 | background-color: #fc6963;
47 | color: #ffffff;
48 | padding: 0 20px;
49 | border-radius: 3px;
50 | pointer-events: all;
51 | text-align: center;
52 | &.cancel {
53 | background: #ffffff;
54 | color: #333333;
55 | }
56 | }
57 | }
58 | `;
--------------------------------------------------------------------------------
/src/pages/Property/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from "react";
2 | import { Container, Images } from "./styles";
3 | import PropTypes from "prop-types";
4 |
5 | import api from "../../services/api";
6 |
7 | const intlMonetary = new Intl.NumberFormat("pt-BR", {
8 | style: "currency",
9 | currency: "BRL",
10 | minimumFractionDigits: 2
11 | });
12 |
13 | export default class Property extends Component {
14 | static propTypes = {
15 | match: PropTypes.shape({
16 | params: PropTypes.shape({
17 | id: PropTypes.string
18 | })
19 | }).isRequired
20 | };
21 | state = {
22 | property: null,
23 | loading: false
24 | };
25 |
26 | async componentDidMount() {
27 | try {
28 | const { id } = this.props.match.params;
29 | this.setState({ loading: true });
30 |
31 | const { data } = await api.get(`/properties/${id}`);
32 | this.setState({ property: data });
33 | } catch (err) {
34 | console.log(err);
35 | } finally {
36 | this.setState({ loading: false });
37 | }
38 | }
39 | renderProperty() {
40 | const { property } = this.state;
41 |
42 | if (!property) {
43 | return "Imóvel não encontrado!";
44 | }
45 |
46 | return (
47 |
48 | {property.title}
49 |
50 | {property.address}
51 |
52 | {property.images.map(image => (
53 |
54 | ))}
55 |
56 | {intlMonetary.format(property.price)}
57 |
58 | );
59 | }
60 |
61 | render() {
62 | const { loading } = this.state;
63 | return (
64 |
65 | {loading ? Carregando
: this.renderProperty()}
66 |
67 | );
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/pages/Property/styles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const Images = styled.div`
4 | width: 100%;
5 | max-width: 660px;
6 | font-size: 16px;
7 | color: #777777;
8 | text-align: center;
9 | display: grid;
10 | grid-template-columns: 100px 100px 100px;
11 | grid-gap: 5px;
12 | background-color: #fff;
13 | color: #444;
14 | &.without-images {
15 | display: flex;
16 | }
17 | img {
18 | width: 100px;
19 | }
20 | p {
21 | margin-top: 15px;
22 | border: none !important;
23 | }
24 | `;
25 |
26 | export const Container = styled.div`
27 | display: flex;
28 | flex-direction: column;
29 | align-items: center;
30 | padding: 10px 20px;
31 | p {
32 | margin-bottom: 15px;
33 | padding: 10px;
34 | width: 100%;
35 | }
36 | hr {
37 | margin: 20px 0;
38 | border: none;
39 | border-bottom: 1px solid #cdcdcd;
40 | width: 100%;
41 | }
42 |
43 | span {
44 | color: #fc6963;
45 | font-size: 20px;
46 | }
47 | `;
48 |
--------------------------------------------------------------------------------
/src/pages/SignIn/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link, withRouter } from "react-router-dom";
3 |
4 | import Logo from "../../assets/airbnb-logo.svg";
5 | import api from "../../services/api";
6 | import { login } from "../../services/auth";
7 |
8 | import { Form, Container } from "./styles";
9 |
10 | class SignIn extends Component {
11 | state = {
12 | email: "",
13 | password: "",
14 | error: ""
15 | };
16 |
17 | handleSignIn = async e => {
18 | e.preventDefault();
19 | const { email, password } = this.state;
20 | if (!email || !password) {
21 | this.setState({ error: "Preencha e-mail e senha para continuar!" });
22 | } else {
23 | try {
24 | const response = await api.post("/sessions", { email, password });
25 | login(response.data.token);
26 | this.props.history.push("/app");
27 | } catch (err) {
28 | this.setState({
29 | error:
30 | "Houve um problema com o login, verifique suas credenciais. T.T"
31 | });
32 | }
33 | }
34 | };
35 |
36 | render() {
37 | return (
38 |
39 |
56 |
57 | );
58 | }
59 | }
60 |
61 | export default withRouter(SignIn);
62 |
--------------------------------------------------------------------------------
/src/pages/SignIn/styles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const Container = styled.div`
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | height: 100vh;
8 | `;
9 |
10 | export const Form = styled.form`
11 | width: 400px;
12 | background: #fff;
13 | padding: 20px;
14 | display: flex;
15 | flex-direction: column;
16 | align-items: center;
17 | img {
18 | width: 100px;
19 | margin: 10px 0 40px;
20 | }
21 | p {
22 | color: #ff3333;
23 | margin-bottom: 15px;
24 | border: 1px solid #ff3333;
25 | padding: 10px;
26 | width: 100%;
27 | text-align: center;
28 | }
29 | input {
30 | flex: 1;
31 | height: 46px;
32 | margin-bottom: 15px;
33 | padding: 0 20px;
34 | color: #777;
35 | font-size: 15px;
36 | width: 100%;
37 | border: 1px solid #ddd;
38 | &::placeholder {
39 | color: #999;
40 | }
41 | }
42 | button {
43 | color: #fff;
44 | font-size: 16px;
45 | background: #fc6963;
46 | height: 56px;
47 | border: 0;
48 | border-radius: 5px;
49 | width: 100%;
50 | }
51 | hr {
52 | margin: 20px 0;
53 | border: none;
54 | border-bottom: 1px solid #cdcdcd;
55 | width: 100%;
56 | }
57 | a {
58 | font-size: 16;
59 | font-weight: bold;
60 | color: #999;
61 | text-decoration: none;
62 | }
63 | `;
64 |
--------------------------------------------------------------------------------
/src/pages/SignUp/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link, withRouter } from "react-router-dom";
3 |
4 | import Logo from "../../assets/airbnb-logo.svg";
5 |
6 | import api from "../../services/api";
7 |
8 | import { Form, Container } from "./styles";
9 |
10 | class SignUp extends Component {
11 | state = {
12 | username: "",
13 | email: "",
14 | password: "",
15 | error: ""
16 | };
17 |
18 | handleSignUp = async e => {
19 | e.preventDefault();
20 | const { username, email, password } = this.state;
21 | if (!username || !email || !password) {
22 | this.setState({ error: "Preencha todos os dados para se cadastrar" });
23 | } else {
24 | try {
25 | await api.post("/users", { username, email, password });
26 | this.props.history.push("/");
27 | } catch (err) {
28 | console.log(err);
29 | this.setState({ error: "Ocorreu um erro ao registrar sua conta. T.T" });
30 | }
31 | }
32 | };
33 |
34 | render() {
35 | return (
36 |
37 |
59 |
60 | );
61 | }
62 | }
63 |
64 | export default withRouter(SignUp);
65 |
--------------------------------------------------------------------------------
/src/pages/SignUp/styles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const Container = styled.div`
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | height: 100vh;
8 | `;
9 |
10 | export const Form = styled.form`
11 | width: 400px;
12 | background: #fff;
13 | padding: 20px;
14 | display: flex;
15 | flex-direction: column;
16 | align-items: center;
17 | img {
18 | width: 100px;
19 | margin: 10px 0 40px;
20 | }
21 | p {
22 | color: #ff3333;
23 | margin-bottom: 15px;
24 | border: 1px solid #ff3333;
25 | padding: 10px;
26 | width: 100%;
27 | text-align: center;
28 | }
29 | input {
30 | flex: 1;
31 | height: 46px;
32 | margin-bottom: 15px;
33 | padding: 0 20px;
34 | color: #777;
35 | font-size: 15px;
36 | width: 100%;
37 | border: 1px solid #ddd;
38 | &::placeholder {
39 | color: #999;
40 | }
41 | }
42 | button {
43 | color: #fff;
44 | font-size: 16px;
45 | background: #fc6963;
46 | height: 56px;
47 | border: 0;
48 | border-radius: 5px;
49 | width: 100%;
50 | }
51 | hr {
52 | margin: 20px 0;
53 | border: none;
54 | border-bottom: 1px solid #cdcdcd;
55 | width: 100%;
56 | }
57 | a {
58 | font-size: 16;
59 | font-weight: bold;
60 | color: #999;
61 | text-decoration: none;
62 | }
63 | `;
64 |
--------------------------------------------------------------------------------
/src/routes.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 | import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
3 | import { ModalContainer } from "react-router-modal";
4 | import "react-router-modal/css/react-router-modal.css";
5 |
6 | import { isAuthenticated } from "./services/auth";
7 |
8 | import SignUp from "./pages/SignUp";
9 | import SignIn from "./pages/SignIn";
10 | import App from "./pages/App";
11 |
12 | const PrivateRoute = ({ component: Component, ...rest }) => (
13 |
16 | isAuthenticated() ? (
17 |
18 | ) : (
19 |
20 | )
21 | }
22 | />
23 | );
24 |
25 | const Routes = () => (
26 |
27 |
28 |
29 |
30 |
31 |
32 | Page not found
} />
33 |
34 |
35 |
36 |
37 | );
38 |
39 | export default Routes;
40 |
--------------------------------------------------------------------------------
/src/services/api.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { getToken } from "./auth";
3 |
4 | const api = axios.create({
5 | baseURL: "http://127.0.0.1:3333"
6 | });
7 |
8 | api.interceptors.request.use(async config => {
9 | const token = getToken();
10 | if (token) {
11 | config.headers.Authorization = `Bearer ${token}`;
12 | }
13 | return config;
14 | });
15 |
16 | export default api;
--------------------------------------------------------------------------------
/src/services/auth.js:
--------------------------------------------------------------------------------
1 | export const TOKEN_KEY = "@airbnb-Token";
2 | export const isAuthenticated = () => localStorage.getItem(TOKEN_KEY) !== null;
3 | export const getToken = () => localStorage.getItem(TOKEN_KEY);
4 | export const login = token => {
5 | localStorage.setItem(TOKEN_KEY, token);
6 | };
7 | export const logout = () => {
8 | localStorage.removeItem(TOKEN_KEY);
9 | };
10 |
--------------------------------------------------------------------------------
/src/styles/global.js:
--------------------------------------------------------------------------------
1 | import { injectGlobal } from "styled-components";
2 |
3 | import "font-awesome/css/font-awesome.css";
4 |
5 | injectGlobal`
6 | * {
7 | box-sizing: border-box;
8 | padding: 0;
9 | margin: 0;
10 | outline: 0;
11 | }
12 | body, html {
13 | background: #eee;
14 | font-family: 'Helvetica Neue', 'Helvetica', Arial, sans-serif;
15 | text-rendering: optimizeLegibility !important;
16 | -webkit-font-smoothing: antialiased !important;
17 | height: 100%;
18 | width: 100%;
19 | }
20 | `;
21 |
--------------------------------------------------------------------------------