├── .gitignore
├── .idea
├── .gitignore
├── inspectionProfiles
│ └── Project_Default.xml
├── instadev.iml
├── jsLibraryMappings.xml
├── misc.xml
├── modules.xml
├── vcs.xml
└── webResources.xml
├── README.md
├── docs
└── instadev.gif
├── package.json
├── public
├── _redirects
├── favicon.ico
├── favicon.png
├── fontawesome.min.css
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── normalize.css
├── robots.txt
└── webfonts
│ ├── fa-brands-400.eot
│ ├── fa-brands-400.svg
│ ├── fa-brands-400.ttf
│ ├── fa-brands-400.woff
│ ├── fa-brands-400.woff2
│ ├── fa-regular-400.eot
│ ├── fa-regular-400.svg
│ ├── fa-regular-400.ttf
│ ├── fa-regular-400.woff
│ ├── fa-regular-400.woff2
│ ├── fa-solid-900.eot
│ ├── fa-solid-900.svg
│ ├── fa-solid-900.ttf
│ ├── fa-solid-900.woff
│ └── fa-solid-900.woff2
├── src
├── __tests__
│ ├── mocks
│ │ ├── comments.json
│ │ ├── likes.json
│ │ ├── posts.json
│ │ ├── stories.json
│ │ └── users.json
│ └── validation.spec.js
├── assets
│ └── img
│ │ ├── instagram-glyph.png
│ │ └── instagram-logo.svg
├── components
│ ├── Loading
│ │ ├── Loading.jsx
│ │ ├── Loading.scss
│ │ └── index.jsx
│ ├── Post
│ │ ├── Post.jsx
│ │ ├── Post.scss
│ │ └── index.jsx
│ ├── Story
│ │ ├── Story.jsx
│ │ ├── Story.scss
│ │ └── index.jsx
│ ├── SuccessMessage
│ │ ├── SuccessMessage.jsx
│ │ ├── SuccessMessage.scss
│ │ └── index.jsx
│ ├── Topbar
│ │ ├── Topbar.jsx
│ │ ├── Topbar.scss
│ │ └── index.jsx
│ └── User
│ │ ├── User.jsx
│ │ └── index.jsx
├── containers
│ ├── App
│ │ ├── App.jsx
│ │ ├── App.scss
│ │ └── index.jsx
│ ├── Posts
│ │ ├── Posts.jsx
│ │ └── index.jsx
│ ├── Stories
│ │ ├── Stories.jsx
│ │ ├── Stories.scss
│ │ └── index.jsx
│ ├── UserForm
│ │ ├── UserForm.jsx
│ │ ├── UserForm.scss
│ │ └── index.jsx
│ ├── UserPosts
│ │ ├── UserPosts.jsx
│ │ ├── UserPosts.scss
│ │ └── index.jsx
│ ├── UserProfile
│ │ ├── UserProfile.jsx
│ │ ├── UserProfile.scss
│ │ └── index.jsx
│ └── UsersList
│ │ ├── UsersList.jsx
│ │ ├── UsersList.scss
│ │ └── index.jsx
├── index.js
├── modules
│ ├── enpoints.js
│ ├── formatter.js
│ └── request.js
├── routes
│ ├── FeedRoute
│ │ ├── FeedRoute.jsx
│ │ ├── FeedRoute.scss
│ │ └── index.jsx
│ ├── NewUserRoute
│ │ ├── NewUserRoute.jsx
│ │ └── index.jsx
│ ├── ProfileRoute
│ │ ├── ProfileRoute.jsx
│ │ └── index.jsx
│ ├── UsersRoute
│ │ ├── UsersRoute.jsx
│ │ └── index.jsx
│ └── index.jsx
├── serviceWorker.js
└── setupTests.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /workspace.xml
3 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/instadev.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/jsLibraryMappings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/webResources.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Instadev React SPA
2 | [](https://app.netlify.com/sites/viniciusvinna-react-instagram/deploys)
3 |
4 | 
5 |
6 | ## Objetivo:
7 | O desafio desta semana e da próxima será desenvolver um `SPA (Single Page Application)` mobile-first do **Instagram** em React, consumindo uma Rest API que deverá cumprir os seguintes critérios:
8 |
9 | > * Ser desenvolvido utilizando abordagem funcional e React Hooks para gerenciamento de ciclo de vida e estados.
10 | > * Consumir os dados da Rest API, usando o [Fetch API](https://developer.mozilla.org/pt-BR/docs/Web/API/Fetch_API/Using_Fetch) do Javascript.
11 | > * Exibir os **posts** de todos os usuários na rota inicial `/`.
12 | > * Exibir todos os **usuários** cadastrados na rota `/users`
13 | > * Exibir o perfil do **usuário** e seus respectivos **posts** na rota `/users/{username}`
14 | > * Exibir os **stories** dos **usuários** na rota inicial `/`.
15 | > * Ao clicar no ícone de **story** do **usuário** deverá abrir seu story com o respectivo vídeo e barra de progresso com a opção de fechar e voltar para o feed (rota inicial).
16 | > * Deve permitir cadastrar um usuário na rota `/newuser` e exibir uma mensagem de alerta ao enviar.
17 |
18 | ### Referência em Produção:
19 | [https://viniciusvinna.netlify.app/react-instagram](https://viniciusvinna.netlify.app/react-instagram)
20 |
21 | ### Repositório Base (para fins de estudo):
22 | [https://github.com/ViniciusVinna/react-instagram](https://github.com/ViniciusVinna/react-instagram)
23 |
24 | ## Requisitos Parte 1:
25 | * Nesta primeira parte você deverá focar apenas na componentização do projeto seguindo os padrões descritos abaixo na seção `Estrutura de Arquivos e Pastas`.
26 | * Não se preocupe com lógica de estados ou requisições http nesta parte, adicione os dados do usuário `hard-code`.
27 |
28 | ### Estrutura de Arquivos e Pastas:
29 | Como já aprendemos em aula, a organização das pastas e arquivos deverá utilizar os seguintes padrões:
30 | * `presentational components` e `container components` -> [Referência](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0)
31 | * `Rails-Style Structure + Module Index` -> [Referência](https://webcache.googleusercontent.com/search?q=cache:DZ0HZwEl7AUJ:https://www.learnhowtoprogram.com/fidgetech-4-react/4-4-advanced-topics/4-4-3-3-react-and-redux-design-patterns+&cd=1&hl=en&ct=clnk&gl=br)
32 |
33 | Seguindo estes dois padrões, a estrutura de arquivos e pastas do seu projeto deverá ficar exatamente assim (dentro de `/src`:
34 | ```bash
35 | ├── assets
36 | │ └── img
37 | │ ├── instagram-glyph.png
38 | │ └── instagram-logo.svg
39 | ├── components
40 | │ ├── Loading
41 | │ │ ├── Loading.jsx
42 | │ │ ├── Loading.scss
43 | │ │ └── index.jsx
44 | │ ├── Post
45 | │ │ ├── Post.jsx
46 | │ │ ├── Post.scss
47 | │ │ └── index.jsx
48 | │ ├── Story
49 | │ │ ├── Story.jsx
50 | │ │ ├── Story.scss
51 | │ │ └── index.jsx
52 | │ ├── SuccessMessage
53 | │ │ ├── SuccessMessage.jsx
54 | │ │ ├── SuccessMessage.scss
55 | │ │ └── index.jsx
56 | │ ├── Topbar
57 | │ │ ├── Topbar.jsx
58 | │ │ ├── Topbar.scss
59 | │ │ └── index.jsx
60 | │ └── User
61 | │ ├── User.jsx
62 | │ └── index.jsx
63 | ├── containers
64 | │ ├── App
65 | │ │ ├── App.jsx
66 | │ │ ├── App.scss
67 | │ │ └── index.jsx
68 | │ ├── Posts
69 | │ │ ├── Posts.jsx
70 | │ │ └── index.jsx
71 | │ ├── Stories
72 | │ │ ├── Stories.jsx
73 | │ │ ├── Stories.scss
74 | │ │ └── index.jsx
75 | │ ├── UserForm
76 | │ │ ├── UserForm.jsx
77 | │ │ ├── UserForm.scss
78 | │ │ └── index.jsx
79 | │ ├── UserPosts
80 | │ │ ├── UserPosts.jsx
81 | │ │ ├── UserPosts.scss
82 | │ │ └── index.jsx
83 | │ ├── UserProfile
84 | │ │ ├── UserProfile.jsx
85 | │ │ ├── UserProfile.scss
86 | │ │ └── index.jsx
87 | │ └── UsersList
88 | │ ├── UsersList.jsx
89 | │ ├── UsersList.scss
90 | │ └── index.jsx
91 | ├── index.js
92 | ├── modules
93 | ├── routes
94 | │ ├── FeedRoute
95 | │ │ ├── FeedRoute.jsx
96 | │ │ ├── FeedRoute.scss
97 | │ │ └── index.jsx
98 | │ ├── NewUserRoute
99 | │ │ ├── NewUserRoute.jsx
100 | │ │ └── index.jsx
101 | │ ├── ProfileRoute
102 | │ │ ├── ProfileRoute.jsx
103 | │ │ └── index.jsx
104 | │ ├── UsersRoute
105 | │ │ ├── UsersRoute.jsx
106 | │ │ └── index.jsx
107 | │ └── index.jsx
108 | ├── serviceWorker.js
109 | └── setupTests.js
110 | ```
111 |
112 | ### IMPORTANTE:
113 | Nesta fase você já foi apresentado ao [testing-library](https://testing-library.com/docs/intro) e para que você seja avaliado corretamente ao submeter seu desafio, é necessário que **TODOS** os seus **elementos React**, como `componentes`, `containers` e `rotas` possuam o atributo JSX/HTML (sintético) `data-testid="{nome-do-componente}"`:
114 |
115 | ```bash
116 | ├── components
117 | │ ├── Loading
118 | │ │ └── Loading.jsx -> data-testid="loading"
119 | │ ├── Post
120 | │ │ └── Post.jsx -> data-testid="post"
121 | │ ├── Story
122 | │ │ └── Story.jsx -> data-testid="story"
123 | │ ├── SuccessMessage
124 | │ │ └── SuccessMessage.jsx -> data-testid="success-message"
125 | │ ├── Topbar
126 | │ │ └── Topbar.jsx -> data-testid="topbar"
127 | │ └── User
128 | │ └── User.jsx -> data-testid="user"
129 | ├── containers
130 | │ ├── App
131 | │ │ └── App.jsx -> data-testid="app"
132 | │ ├── Posts
133 | │ │ └── Posts.jsx -> data-testid="posts"
134 | │ ├── Stories
135 | │ │ └── Stories.jsx -> data-testid="stories"
136 | │ ├── UserForm
137 | │ │ └── UserForm.jsx -> data-testid="user-form"
138 | │ ├── UserPosts
139 | │ │ └── UserPosts.jsx -> data-testid="user-posts"
140 | │ ├── UserProfile
141 | │ │ └── UserProfile.jsx -> data-testid="user-profile"
142 | │ └── UsersList
143 | │ └── UsersList.jsx -> data-testid="user-list"
144 | └── routes
145 | ├── FeedRoute
146 | │ └── FeedRoute.jsx -> data-testid="feed-route"
147 | ├── NewUserRoute
148 | │ └── NewUserRoute.jsx -> data-testid="new-user-route"
149 | ├── ProfileRoute
150 | │ └── ProfileRoute.jsx -> data-testid="profile-route"
151 | └── UsersRoute
152 | └── UsersRoute.jsx -> data-testid="users-route"
153 | ```
154 |
155 | ### Iniciar o projeto:
156 | * Instale as dependências do projeto com o comando `yarn install` ou `npm install`.
157 | * Inicie o projeto com comando `yarn start` ou `npm start` / `npm run start`.
158 |
159 | ## REST API
160 | ### Usuários
161 | | Resource | Method | Endpoint | Status Code | Response |
162 | |:--------:|:---------:|--------------------------------------------------------------|:-----------:|:---------------:|
163 | | Users | GET | https://5e7d0266a917d70016684219.mockapi.io/api/v1/users | 200 | Array of Object |
164 | | User | GET | https://5e7d0266a917d70016684219.mockapi.io/api/v1/users/:id | 200 | Object |
165 | | User | POST | https://5e7d0266a917d70016684219.mockapi.io/api/v1/users | 201 | Created object |
166 | | User | PUT | https://5e7d0266a917d70016684219.mockapi.io/api/v1/users/:id | 200 | Updated object |
167 | | User | DELETE | https://5e7d0266a917d70016684219.mockapi.io/api/v1/users/:id | 200 | Deleted object |
168 |
169 | > Body (POST / PUT):
170 | ```json
171 | {
172 | "name": "string",
173 | "avatar": "string",
174 | "username": "string",
175 | "email": "string"
176 | }
177 | ```
178 |
179 | ### Stories
180 | | Resource | Method | Endpoint | Status Code | Response |
181 | |:--------:|:---------:|-----------------------------------------------------------------|:-----------:|:---------------:|
182 | | stories | GET | https://5e7d0266a917d70016684219.mockapi.io/api/v1/stories | 200 | Array of Object |
183 | | story | GET | https://5e7d0266a917d70016684219.mockapi.io/api/v1/stories/:id | 200 | Object |
184 |
185 | ### Posts
186 | | Resource | Method | Endpoint | Status Code | Response |
187 | |:--------:|:-------:|------------------------------------------------------------------------|-------------|-----------------|
188 | | Posts | GET | https://5e7d0266a917d70016684219.mockapi.io/api/v1/users/:id/posts | 200 | Array of Object |
189 | | Post | GET | https://5e7d0266a917d70016684219.mockapi.io/api/v1/users/:id/posts/:id | 200 | Object |
190 | | Post | POST | https://5e7d0266a917d70016684219.mockapi.io/api/v1/users/:id/posts | 201 | Created object |
191 | | Post | PUT | https://5e7d0266a917d70016684219.mockapi.io/api/v1/users/:id/posts/:id | 200 | Updated object |
192 | | Post | DELETE | https://5e7d0266a917d70016684219.mockapi.io/api/v1/users/:id/posts/:id | 200 | Deleted object |
193 |
194 | > Body (POST / PUT):
195 | ```json
196 | {
197 | "userId": "string",
198 | "imageUrl": "string"
199 | }
200 | ```
201 |
202 | ### Comentários
203 | | Resource | Method | Endpoint | Status Code | Response |
204 | |:--------:|:-------:|----------------------------------------------------------------------------------------|-------------|-----------------|
205 | | Comments | GET | https://5e7d0266a917d70016684219.mockapi.io/api/v1/users/:id/posts/:id/comments | 200 | Array of Object |
206 | | Comment | GET | https://5e7d0266a917d70016684219.mockapi.io/api/v1/users/:id/posts/:id/comments/:id | 200 | Object |
207 | | Comment | POST | https://5e7d0266a917d70016684219.mockapi.io/api/v1/users/:id/posts/:id/comments | 201 | Created object |
208 | | Comment | PUT | https://5e7d0266a917d70016684219.mockapi.io/api/v1/users/:id/posts/:id/comments/:id | 200 | Updated object |
209 | | Comment | DELETE | https://5e7d0266a917d70016684219.mockapi.io/api/v1/users/:id/posts/:id/comments/:id | 200 | Deleted object |
210 |
211 | > Body (POST / PUT):
212 | ```json
213 | {
214 | "postId": "string",
215 | "comment": "string",
216 | "avatar": "string",
217 | "name": "string"
218 | }
219 | ```
220 |
221 | ### Query Params
222 | > Adicione os seguintes **queries** às requisições `GET`:
223 | > #### Paginação
224 | > `?page=1&limit=10` ou `?p=1&l=10`
225 | >
226 | > #### Ordenação
227 | > `?sortBy=createdAt&order=desc`
228 | > também é possível utilizar `sortby`, `orderBy`, ou `orderby`
229 | > OBS: se você omitir o parâmetro `order`, a ordenação padrão será 'asc'
230 | >
231 | > #### Busca
232 | > `?search=blog1` ou `?filter=blog1`
233 |
234 | ### Códigos de erro e mensagens de retorno
235 | * `200` - OK
236 | * `201` - OK
237 | * `404` - Not found
238 | * `500` - Server error
239 |
240 | ## Tópicos:
241 | Neste desafio você vai praticar os seus conhecimentos em:
242 | - **Fetch API**
243 | - **JS Funcional:**
244 | - **Modularização**
245 | - **Presentational e Container Components**
246 | - **Rails-Style Structure + Module Index**
247 | - **React Hooks** `useState`, `useEffect`
248 | - **React Testing Library**
249 | - **React**
250 |
251 | ## Requisitos:
252 | * **[Node v13.8.0](https://nodejs.org/en/)** - ou superior, instalado em seu computador.
253 | * **[Create React App](https://github.com/facebook/create-react-app)**
254 |
--------------------------------------------------------------------------------
/docs/instadev.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/docs/instadev.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "instadev",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.3.2",
8 | "@testing-library/user-event": "^7.1.2",
9 | "history": "^4.10.1",
10 | "node-sass": "^4.13.1",
11 | "prop-types": "^15.7.2",
12 | "react": "^16.13.1",
13 | "react-dom": "^16.13.1",
14 | "react-router-dom": "^5.1.2",
15 | "react-scripts": "3.4.1"
16 | },
17 | "scripts": {
18 | "start": "react-scripts start",
19 | "build": "react-scripts build",
20 | "test": "react-scripts test",
21 | "eject": "react-scripts eject"
22 | },
23 | "eslintConfig": {
24 | "extends": "react-app"
25 | },
26 | "browserslist": {
27 | "production": [
28 | ">0.2%",
29 | "not dead",
30 | "not op_mini all"
31 | ],
32 | "development": [
33 | "last 1 chrome version",
34 | "last 1 firefox version",
35 | "last 1 safari version"
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
2 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/favicon.png
--------------------------------------------------------------------------------
/public/fontawesome.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome Free 5.12.1 by @fontawesome - https://fontawesome.com
3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
4 | */
5 | .fa,.fab,.fad,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-bahai:before{content:"\f666"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buy-n-large:before{content:"\f8a6"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caravan:before{content:"\f8ff"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-alt:before{content:"\f422"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-cotton-bureau:before{content:"\f89e"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dailymotion:before{content:"\f952"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-alt:before{content:"\f424"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-firefox-browser:before{content:"\f907"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-hat-wizard:before{content:"\f6e8"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-ideal:before{content:"\f913"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-instagram-square:before{content:"\f955"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-mdb:before{content:"\f8ca"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microblog:before{content:"\f91a"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mixer:before{content:"\f956"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse:before{content:"\f8cc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-orcid:before{content:"\f8d2"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-square:before{content:"\f91e"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-record-vinyl:before{content:"\f8d9"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopify:before{content:"\f957"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swift:before{content:"\f8e1"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-trailer:before{content:"\f941"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbraco:before{content:"\f8e8"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-unity:before{content:"\f949"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:400;font-display:auto;src:url(webfonts/fa-brands-400.eot);src:url(webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(webfonts/fa-brands-400.woff2) format("woff2"),url(webfonts/fa-brands-400.woff) format("woff"),url(webfonts/fa-brands-400.ttf) format("truetype"),url(webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:auto;src:url(webfonts/fa-regular-400.eot);src:url(webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(webfonts/fa-regular-400.woff2) format("woff2"),url(webfonts/fa-regular-400.woff) format("woff"),url(webfonts/fa-regular-400.ttf) format("truetype"),url(webfonts/fa-regular-400.svg#fontawesome) format("svg")}.fab,.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:auto;src:url(webfonts/fa-solid-900.eot);src:url(webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(webfonts/fa-solid-900.woff2) format("woff2"),url(webfonts/fa-solid-900.woff) format("woff"),url(webfonts/fa-solid-900.ttf) format("truetype"),url(webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900}
6 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
30 | Instadev
31 |
32 |
33 |
34 |
35 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Instadev - React",
3 | "name": "Vinicius Ribeiro's Challenge",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#08111E",
24 | "background_color": "#08111E"
25 | }
26 |
--------------------------------------------------------------------------------
/public/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS and IE text size adjust after device orientation change,
6 | * without disabling user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | * and Firefox.
30 | * Correct `block` display not defined for `main` in IE 11.
31 | */
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | /**
50 | * 1. Correct `inline-block` display not defined in IE 8/9.
51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | */
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; /* 1 */
59 | vertical-align: baseline; /* 2 */
60 | }
61 |
62 | /**
63 | * Prevent modern browsers from displaying `audio` without controls.
64 | * Remove excess height in iOS 5 devices.
65 | */
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | /**
73 | * Address `[hidden]` styling not present in IE 8/9/10.
74 | * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
75 | */
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | /* Links
83 | ========================================================================== */
84 |
85 | /**
86 | * Remove the gray background color from active links in IE 10.
87 | */
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | /**
94 | * Improve readability of focused elements when they are also in an
95 | * active/hover state.
96 | */
97 |
98 | a:active,
99 | a:hover {
100 | outline: 0;
101 | }
102 |
103 | /* Text-level semantics
104 | ========================================================================== */
105 |
106 | /**
107 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
108 | */
109 |
110 | abbr[title] {
111 | border-bottom: 1px dotted;
112 | }
113 |
114 | /**
115 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
116 | */
117 |
118 | b,
119 | strong {
120 | font-weight: bold;
121 | }
122 |
123 | /**
124 | * Address styling not present in Safari and Chrome.
125 | */
126 |
127 | dfn {
128 | font-style: italic;
129 | }
130 |
131 | /**
132 | * Address variable `h1` font-size and margin within `section` and `article`
133 | * contexts in Firefox 4+, Safari, and Chrome.
134 | */
135 |
136 | h1 {
137 | font-size: 2em;
138 | margin: 0.67em 0;
139 | }
140 |
141 | /**
142 | * Address styling not present in IE 8/9.
143 | */
144 |
145 | mark {
146 | background: #ff0;
147 | color: #000;
148 | }
149 |
150 | /**
151 | * Address inconsistent and variable font size in all browsers.
152 | */
153 |
154 | small {
155 | font-size: 80%;
156 | }
157 |
158 | /**
159 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
160 | */
161 |
162 | sub,
163 | sup {
164 | font-size: 75%;
165 | line-height: 0;
166 | position: relative;
167 | vertical-align: baseline;
168 | }
169 |
170 | sup {
171 | top: -0.5em;
172 | }
173 |
174 | sub {
175 | bottom: -0.25em;
176 | }
177 |
178 | /* Embedded content
179 | ========================================================================== */
180 |
181 | /**
182 | * Remove border when inside `a` element in IE 8/9/10.
183 | */
184 |
185 | img {
186 | border: 0;
187 | }
188 |
189 | /**
190 | * Correct overflow not hidden in IE 9/10/11.
191 | */
192 |
193 | svg:not(:root) {
194 | overflow: hidden;
195 | }
196 |
197 | /* Grouping content
198 | ========================================================================== */
199 |
200 | /**
201 | * Address margin not present in IE 8/9 and Safari.
202 | */
203 |
204 | figure {
205 | margin: 1em 40px;
206 | }
207 |
208 | /**
209 | * Address differences between Firefox and other browsers.
210 | */
211 |
212 | hr {
213 | box-sizing: content-box;
214 | height: 0;
215 | }
216 |
217 | /**
218 | * Contain overflow in all browsers.
219 | */
220 |
221 | pre {
222 | overflow: auto;
223 | }
224 |
225 | /**
226 | * Address odd `em`-unit font size rendering in all browsers.
227 | */
228 |
229 | code,
230 | kbd,
231 | pre,
232 | samp {
233 | font-family: monospace, monospace;
234 | font-size: 1em;
235 | }
236 |
237 | /* Forms
238 | ========================================================================== */
239 |
240 | /**
241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
242 | * styling of `select`, unless a `border` property is set.
243 | */
244 |
245 | /**
246 | * 1. Correct color not being inherited.
247 | * Known issue: affects color of disabled elements.
248 | * 2. Correct font properties not being inherited.
249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
250 | */
251 |
252 | button,
253 | input,
254 | optgroup,
255 | select,
256 | textarea {
257 | color: inherit; /* 1 */
258 | font: inherit; /* 2 */
259 | margin: 0; /* 3 */
260 | }
261 |
262 | /**
263 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
264 | */
265 |
266 | button {
267 | overflow: visible;
268 | }
269 |
270 | /**
271 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
272 | * All other form control elements do not inherit `text-transform` values.
273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
274 | * Correct `select` style inheritance in Firefox.
275 | */
276 |
277 | button,
278 | select {
279 | text-transform: none;
280 | }
281 |
282 | /**
283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
284 | * and `video` controls.
285 | * 2. Correct inability to style clickable `input` types in iOS.
286 | * 3. Improve usability and consistency of cursor style between image-type
287 | * `input` and others.
288 | */
289 |
290 | button,
291 | html input[type="button"], /* 1 */
292 | input[type="reset"],
293 | input[type="submit"] {
294 | -webkit-appearance: button; /* 2 */
295 | cursor: pointer; /* 3 */
296 | }
297 |
298 | /**
299 | * Re-set default cursor for disabled elements.
300 | */
301 |
302 | button[disabled],
303 | html input[disabled] {
304 | cursor: default;
305 | }
306 |
307 | /**
308 | * Remove inner padding and border in Firefox 4+.
309 | */
310 |
311 | button::-moz-focus-inner,
312 | input::-moz-focus-inner {
313 | border: 0;
314 | padding: 0;
315 | }
316 |
317 | /**
318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
319 | * the UA stylesheet.
320 | */
321 |
322 | input {
323 | line-height: normal;
324 | }
325 |
326 | /**
327 | * It's recommended that you don't attempt to style these elements.
328 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
329 | *
330 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
331 | * 2. Remove excess padding in IE 8/9/10.
332 | */
333 |
334 | input[type="checkbox"],
335 | input[type="radio"] {
336 | box-sizing: border-box; /* 1 */
337 | padding: 0; /* 2 */
338 | }
339 |
340 | /**
341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
342 | * `font-size` values of the `input`, it causes the cursor style of the
343 | * decrement button to change from `default` to `text`.
344 | */
345 |
346 | input[type="number"]::-webkit-inner-spin-button,
347 | input[type="number"]::-webkit-outer-spin-button {
348 | height: auto;
349 | }
350 |
351 | /**
352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
354 | */
355 |
356 | input[type="search"] {
357 | -webkit-appearance: textfield; /* 1 */
358 | box-sizing: content-box; /* 2 */
359 | }
360 |
361 | /**
362 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
363 | * Safari (but not Chrome) clips the cancel button when the search input has
364 | * padding (and `textfield` appearance).
365 | */
366 |
367 | input[type="search"]::-webkit-search-cancel-button,
368 | input[type="search"]::-webkit-search-decoration {
369 | -webkit-appearance: none;
370 | }
371 |
372 | /**
373 | * Define consistent border, margin, and padding.
374 | */
375 |
376 | fieldset {
377 | border: 1px solid #c0c0c0;
378 | margin: 0 2px;
379 | padding: 0.35em 0.625em 0.75em;
380 | }
381 |
382 | /**
383 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
384 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
385 | */
386 |
387 | legend {
388 | border: 0; /* 1 */
389 | padding: 0; /* 2 */
390 | }
391 |
392 | /**
393 | * Remove default vertical scrollbar in IE 8/9/10/11.
394 | */
395 |
396 | textarea {
397 | overflow: auto;
398 | }
399 |
400 | /**
401 | * Don't inherit the `font-weight` (applied by a rule above).
402 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
403 | */
404 |
405 | optgroup {
406 | font-weight: bold;
407 | }
408 |
409 | /* Tables
410 | ========================================================================== */
411 |
412 | /**
413 | * Remove most spacing between table cells.
414 | */
415 |
416 | table {
417 | border-collapse: collapse;
418 | border-spacing: 0;
419 | }
420 |
421 | td,
422 | th {
423 | padding: 0;
424 | }
425 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/webfonts/fa-brands-400.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/webfonts/fa-brands-400.eot
--------------------------------------------------------------------------------
/public/webfonts/fa-brands-400.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/webfonts/fa-brands-400.ttf
--------------------------------------------------------------------------------
/public/webfonts/fa-brands-400.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/webfonts/fa-brands-400.woff
--------------------------------------------------------------------------------
/public/webfonts/fa-brands-400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/webfonts/fa-brands-400.woff2
--------------------------------------------------------------------------------
/public/webfonts/fa-regular-400.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/webfonts/fa-regular-400.eot
--------------------------------------------------------------------------------
/public/webfonts/fa-regular-400.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/webfonts/fa-regular-400.ttf
--------------------------------------------------------------------------------
/public/webfonts/fa-regular-400.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/webfonts/fa-regular-400.woff
--------------------------------------------------------------------------------
/public/webfonts/fa-regular-400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/webfonts/fa-regular-400.woff2
--------------------------------------------------------------------------------
/public/webfonts/fa-solid-900.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/webfonts/fa-solid-900.eot
--------------------------------------------------------------------------------
/public/webfonts/fa-solid-900.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/webfonts/fa-solid-900.ttf
--------------------------------------------------------------------------------
/public/webfonts/fa-solid-900.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/webfonts/fa-solid-900.woff
--------------------------------------------------------------------------------
/public/webfonts/fa-solid-900.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/public/webfonts/fa-solid-900.woff2
--------------------------------------------------------------------------------
/src/__tests__/mocks/comments.json:
--------------------------------------------------------------------------------
1 | [{"id":"1","postId":"1","createdAt":"2020-03-27T00:58:06.401Z","comment":"programming the transmitter won't do anything, we need to override the back-end PNG bus!","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/yayteejay/128.jpg","name":"Santino Rowe","meta":{"id":"1","userId":"1","imageUrl":"https://viniciusvinna.netlify.app/assets//api-instagram/profiles/black-panther/black-panther-1.jpg"}},{"id":"31","postId":"1","createdAt":"2020-03-27T04:04:36.486Z","comment":"I'll navigate the digital SCSI matrix, that should protocol the SAS pixel!","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/kosmar/128.jpg","name":"Virginia Kshlerin","meta":{"id":"1","userId":"1","imageUrl":"https://viniciusvinna.netlify.app/assets//api-instagram/profiles/black-panther/black-panther-1.jpg"}}]
--------------------------------------------------------------------------------
/src/__tests__/mocks/likes.json:
--------------------------------------------------------------------------------
1 | [{"id":"1","postId":"1","meta":{"id":"1","userId":"1","imageUrl":"https://viniciusvinna.netlify.app/assets//api-instagram/profiles/black-panther/black-panther-1.jpg"}}]
--------------------------------------------------------------------------------
/src/__tests__/mocks/posts.json:
--------------------------------------------------------------------------------
1 | [{"id":"1","userId":"1","imageUrl":"https://viniciusvinna.netlify.app/assets//api-instagram/profiles/black-panther/black-panther-1.jpg","comments":[{"id":"1","postId":"1","createdAt":"2020-03-27T00:58:06.401Z","comment":"programming the transmitter won't do anything, we need to override the back-end PNG bus!","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/yayteejay/128.jpg","name":"Santino Rowe"},{"id":"31","postId":"1","createdAt":"2020-03-27T04:04:36.486Z","comment":"I'll navigate the digital SCSI matrix, that should protocol the SAS pixel!","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/kosmar/128.jpg","name":"Virginia Kshlerin"}],"likes":[{"id":"1","postId":"1"}]},{"id":"7","userId":"1","imageUrl":"https://viniciusvinna.netlify.app/assets//api-instagram/profiles/black-panther/black-panther-2.jpg","comments":[{"id":"7","postId":"7","createdAt":"2020-03-26T22:13:09.348Z","comment":"Use the digital XML firewall, then you can hack the bluetooth card!","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/adamawesomeface/128.jpg","name":"Delfina Bahringer"},{"id":"37","postId":"7","createdAt":"2020-03-26T23:54:44.131Z","comment":"Try to compress the COM capacitor, maybe it will bypass the wireless feed!","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/ultragex/128.jpg","name":"Felton Crooks"}],"likes":[{"id":"7","postId":"7"}]},{"id":"13","userId":"1","imageUrl":"https://viniciusvinna.netlify.app/assets//api-instagram/profiles/black-panther/black-panther-3.jpg","comments":[{"id":"13","postId":"13","createdAt":"2020-03-26T08:16:49.988Z","comment":"You can't copy the program without parsing the wireless JSON feed!","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/toddrew/128.jpg","name":"Damien Hodkiewicz"},{"id":"43","postId":"13","createdAt":"2020-03-26T14:00:15.130Z","comment":"Use the neural PNG matrix, then you can quantify the haptic protocol!","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/j2deme/128.jpg","name":"Theodora Turner"}],"likes":[{"id":"13","postId":"13"}]},{"id":"19","userId":"1","imageUrl":"https://viniciusvinna.netlify.app/assets//api-instagram/profiles/black-panther/black-panther-4.jpg","comments":[{"id":"19","postId":"19","createdAt":"2020-03-26T09:02:31.626Z","comment":"The PNG bandwidth is down, transmit the digital pixel so we can synthesize the JSON pixel!","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/baliomega/128.jpg","name":"Delbert Schneider"},{"id":"49","postId":"19","createdAt":"2020-03-26T15:33:40.603Z","comment":"Use the bluetooth THX interface, then you can copy the online system!","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/mr_shiznit/128.jpg","name":"Anastasia Waters"}],"likes":[{"id":"19","postId":"19"}]},{"id":"25","userId":"1","imageUrl":"https://viniciusvinna.netlify.app/assets//api-instagram/profiles/black-panther/black-panther-5.jpg","comments":[{"id":"25","postId":"25","createdAt":"2020-03-27T00:37:32.914Z","comment":"I'll navigate the back-end GB monitor, that should port the CSS system!","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/jagan123/128.jpg","name":"Bonnie Rosenbaum"},{"id":"55","postId":"25","createdAt":"2020-03-27T00:08:25.004Z","comment":"Try to input the EXE application, maybe it will compress the wireless program!","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/de_ascanio/128.jpg","name":"Marisol Thompson"}],"likes":[{"id":"25","postId":"25"}]}]
--------------------------------------------------------------------------------
/src/__tests__/mocks/stories.json:
--------------------------------------------------------------------------------
1 | [{"id":"1","userId":"1","videoUrl":"https://viniciusvinna.netlify.app/assets/api-instagram/profiles/black-panther/black-panther-stories.mp4"},{"id":"2","userId":"2","videoUrl":"https://viniciusvinna.netlify.app/assets/api-instagram/profiles/bruce/bruce-stories.mp4"},{"id":"3","userId":"5","videoUrl":"https://viniciusvinna.netlify.app/assets/api-instagram/profiles/gamora/gamora-stories.mp4"}]
--------------------------------------------------------------------------------
/src/__tests__/mocks/users.json:
--------------------------------------------------------------------------------
1 | [{"id":"1","name":"T'Challa","avatar":"https://viniciusvinna.netlify.app/assets//api-instagram/profiles/black-panther/black-panther-profile.jpg","username":"blackpanther","email":"blackpanther@gmail.com"},{"id":"2","name":"Bruce Wayne","avatar":"https://viniciusvinna.netlify.app/assets//api-instagram/profiles/bruce/bruce-profile.jpg","username":"brucewayne","email":"iamnotthebatman@hotmail.com"},{"id":"3","name":"Carol Danvers","avatar":"https://viniciusvinna.netlify.app/assets//api-instagram/profiles/carol/carol-profile.jpg","username":"captainmarvel","email":"captainmarvel@yahoo.com"},{"id":"4","name":"Domino","avatar":"https://viniciusvinna.netlify.app/assets//api-instagram/profiles/domino/domino-profile.jpg","username":"domino","email":"domino@hotmail.com"},{"id":"5","name":"Gamora","avatar":"https://viniciusvinna.netlify.app/assets//api-instagram/profiles/gamora/gamora-profile.jpg","username":"gamora","email":"Abigayle.Kemmer32@yahoo.com"},{"id":"6","name":"Mestre Yoda","avatar":"https://viniciusvinna.netlify.app/assets//api-instagram/profiles/yoda/yoda-profile.jpg","username":"mestrejedi","email":"yoda@gmail.com"},{"id":"7","name":"Vinna","avatar":"https://media-exp1.licdn.com/dms/image/C5603AQHTgizSqSTETg/profile-displayphoto-shrink_200_200/0?e=1591228800&v=beta&t=TFz7U7fVtPsbhPwyMti7vxa2EV-MagPZSPB10fVMP00","username":"thevinnacomedia","email":"vinna@hotmail.com"},{"id":"8","name":"Hedênica Morais","avatar":"https://media-exp1.licdn.com/dms/image/C4D03AQEbY-NsBLjfDQ/profile-displayphoto-shrink_200_200/0?e=1591228800&v=beta&t=qbrMFFRTaa4hCJtajlTHHQzQyqQ9OdP5eleR0TvnWcY","username":"denny","email":"hedenica@hotmail.com"},{"id":"9","name":"Marina Tujimura","avatar":"https://media-exp1.licdn.com/dms/image/C4E03AQF4m60UnP3l_A/profile-displayphoto-shrink_200_200/0?e=1591228800&v=beta&t=Z0sB-fo-g02YAG_IsIjLWjwsoxI1UI_tPdNpBQvBhn0","username":"tuji","email":"mtujimura@gmail.com"},{"id":"10","name":"Paloma Correa","avatar":"https://media-exp1.licdn.com/dms/image/C4E03AQGEOX3z7y-_Lw/profile-displayphoto-shrink_200_200/0?e=1591228800&v=beta&t=Iuvpl_-StJrxtGhhPRic1HPmIsKchjmRpE66EUO7QJ8","username":"palomacorrea","email":"paloma@email.com"},{"id":"11","name":"thierry rene matos","avatar":"https://media-exp1.licdn.com/dms/image/C4D03AQFAf-5eABXhLg/profile-displayphoto-shrink_100_100/0?e=1591228800&v=beta&t=14sgXfCa8nRu12qmeSX8pCr--Vflw4DDFEZ6RTXGW0M","username":"thierryrene","email":"teste@teste.com"},{"id":"12","name":"Chicão","avatar":"https://media-exp1.licdn.com/dms/image/C4D03AQGSnLxpcjS2MQ/profile-displayphoto-shrink_200_200/0?e=1591228800&v=beta&t=-oxSRBcevGLa7o3W0H-1_CLbYKXIkAwbGnZ4VZ14oP0","username":"iamchicao","email":"jr-francis2011@hotmail.com"},{"id":"14","name":"Raissa Barata","avatar":"https://media-exp1.licdn.com/dms/image/C4E03AQEiAT18LS-6iA/profile-displayphoto-shrink_200_200/0?e=1591228800&v=beta&t=iGTl5Q5p9yhSsj4ZIdGSe0midvFk83h8imPbxGqK5gU","username":"raissamariab","email":"raissamaria.sb@gmail.com"},{"id":"16","name":"Mariana Zangrossi","avatar":"https://media-exp1.licdn.com/dms/image/C4D03AQFzQ4ZinuaZZA/profile-displayphoto-shrink_200_200/0?e=1591833600&v=beta&t=lHCTNprrsgTJSM7aPeBa9RTPGYFkLVZjICt9IM13b84","username":"marianazangrossi","email":"mari.zangue@gmail.com"},{"id":"17","name":"Nicoli","avatar":"https://media-exp1.licdn.com/dms/image/C4E03AQHt1a1J-QyBLg/profile-displayphoto-shrink_200_200/0?e=1591833600&v=beta&t=FfhCnWZebxjQdCOtUU0F0gMHbZoe_3oLu-X_W1j8lP0","username":"nicolifelix","email":""},{"id":"18","name":"Dara","avatar":"https://media-exp1.licdn.com/dms/image/C4D03AQFoITQkJLXC0w/profile-displayphoto-shrink_200_200/0?e=1591833600&v=beta&t=dj70fKZUW8lBMNg1o8B3OLmOYxhFLTdNQaOoJdgbLtw","username":"arad","email":"daraamedeiros@outlook.com"},{"id":"20","name":"mariana","avatar":"https://media-exp1.licdn.com/dms/image/C4D03AQFzQ4ZinuaZZA/profile-displayphoto-shrink_200_200/0?e=1591833600&v=beta&t=lHCTNprrsgTJSM7aPeBa9RTPGYFkLVZjICt9IM13b84","username":"mariana","email":"mariana@gmail.com"},{"id":"21","name":"John Doe","avatar":"","username":"johndoe","email":"johndoe@gmail.com"}]
--------------------------------------------------------------------------------
/src/__tests__/validation.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '@testing-library/jest-dom/extend-expect';
3 | import { createMemoryHistory } from 'history';
4 | import { Router } from 'react-router-dom';
5 |
6 | import { render } from '@testing-library/react';
7 |
8 | import Loading from '../components/Loading';
9 | import Post from '../components/Post';
10 | import Story from '../components/Story';
11 | import SuccessMessage from '../components/SuccessMessage';
12 | import Topbar from '../components/Topbar';
13 | import User from '../components/User';
14 |
15 | import App from '../containers/App';
16 | import Posts from '../containers/Posts';
17 | import Stories from '../containers/Stories';
18 | import UserForm from '../containers/UserForm';
19 | import UserPosts from '../containers/UserPosts';
20 | import UserProfile from '../containers/UserProfile';
21 |
22 | import FeedRoute from '../routes/FeedRoute';
23 | import NewUserRoute from '../routes/NewUserRoute';
24 | import ProfileRoute from '../routes/ProfileRoute';
25 | import UsersRoute from '../routes/UsersRoute';
26 |
27 | const history = createMemoryHistory();
28 |
29 | describe('React Instagram APP', () => {
30 | describe('Folder Strucutre and data-test-id attributes', () => {
31 | it('should should render properly the component', () => {
32 | const { getByTestId } = render();
33 | const container = getByTestId('loading');
34 |
35 | expect(container).toBeDefined();
36 | });
37 |
38 | it('should should render properly the component', () => {
39 | const propsMock = {
40 | postInfo: { comments: [] },
41 | userInfo: {},
42 | };
43 |
44 | const { getByTestId } = render(
45 |
46 |
47 |
48 | );
49 | const container = getByTestId('post');
50 |
51 | expect(container).toBeDefined();
52 | });
53 |
54 | it('should should render properly the component', () => {
55 | const propsMock = {
56 | story: {},
57 | user: { username: 'Vinna'},
58 | handleClose: {}
59 |
60 | };
61 | const { getByTestId } = render(
62 |
63 |
64 |
65 | );
66 | const container = getByTestId('story');
67 |
68 | expect(container).toBeDefined();
69 | });
70 |
71 | it('should should render properly the component', () => {
72 | const { getByTestId } = render(
73 |
74 |
75 |
76 | );
77 | const container = getByTestId('success-message');
78 |
79 | expect(container).toBeDefined();
80 | });
81 |
82 | it('should should render properly the component', () => {
83 | const { getByTestId } = render(
84 |
85 |
86 |
87 | );
88 | const container = getByTestId('topbar');
89 |
90 | expect(container).toBeDefined();
91 | });
92 |
93 | it('should should render properly the component', () => {
94 | const propsMock = {
95 | infoUser: {
96 | avatar: '',
97 | name: '',
98 | username: '',
99 | }
100 | };
101 |
102 | const { getByTestId } = render(
103 |
104 |
105 |
106 | );
107 | const container = getByTestId('user');
108 |
109 | expect(container).toBeDefined();
110 | });
111 |
112 | it('should should render properly the container', () => {
113 | const { getByTestId } = render(
114 |
115 |
116 |
117 | );
118 | const container = getByTestId('app');
119 |
120 | expect(container).toBeDefined();
121 | });
122 |
123 | it('should should render properly the container', () => {
124 | const propsMock = {
125 | posts: [],
126 | };
127 |
128 | const { getByTestId } = render(
129 |
130 |
131 |
132 | );
133 | const container = getByTestId('posts');
134 |
135 | expect(container).toBeDefined();
136 | });
137 |
138 | it('should should render properly the container', () => {
139 | const propsMock = {
140 | stories: [],
141 | };
142 |
143 | const { getByTestId } = render(
144 |
145 |
146 |
147 | );
148 |
149 | const container = getByTestId('stories');
150 |
151 | expect(container).toBeDefined();
152 | });
153 |
154 | it('should should render properly the container', () => {
155 | const { getByTestId } = render(
156 |
157 |
158 |
159 | );
160 | const container = getByTestId('user-form');
161 |
162 | expect(container).toBeDefined();
163 | });
164 |
165 | it('should should render properly the container', () => {
166 | const propsMock = {
167 | posts: [],
168 | };
169 |
170 | const { getByTestId } = render(
171 |
172 |
173 |
174 | );
175 | const container = getByTestId('user-posts');
176 |
177 | expect(container).toBeDefined();
178 | });
179 |
180 | it('should should render properly the container', () => {
181 | const propsMock = {
182 | avatar: '',
183 | name: '',
184 | username: '',
185 | };
186 |
187 | const { getByTestId } = render(
188 |
189 |
190 |
191 | );
192 | const container = getByTestId('user-profile');
193 |
194 | expect(container).toBeDefined();
195 | });
196 | });
197 |
198 | it('should should render properly the component', () => {
199 | const { getByTestId } = render(
200 |
201 |
202 |
203 | );
204 | const container = getByTestId('feed-route');
205 |
206 | expect(container).toBeDefined();
207 | });
208 |
209 | it('should should render properly the component', () => {
210 | const { getByTestId } = render(
211 |
212 |
213 |
214 | );
215 | const container = getByTestId('new-user-route');
216 |
217 | expect(container).toBeDefined();
218 | });
219 |
220 | it('should should render properly the component', () => {
221 | const { getByTestId } = render(
222 |
223 |
224 |
225 | );
226 |
227 | const container = getByTestId('profile-route');
228 |
229 | expect(container).toBeDefined();
230 | });
231 |
232 | it('should should render properly the component', () => {
233 | const { getByTestId } = render(
234 |
235 |
236 |
237 | );
238 | const container = getByTestId('users-route');
239 |
240 | expect(container).toBeDefined();
241 | });
242 | });
243 |
244 |
--------------------------------------------------------------------------------
/src/assets/img/instagram-glyph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/src/assets/img/instagram-glyph.png
--------------------------------------------------------------------------------
/src/assets/img/instagram-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/Loading/Loading.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import './Loading.scss';
4 |
5 | const Loading = () => (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Carregando
17 |
18 | );
19 |
20 | export default Loading;
21 |
--------------------------------------------------------------------------------
/src/components/Loading/Loading.scss:
--------------------------------------------------------------------------------
1 | .loading {
2 | align-items: center;
3 | color: #16202B;
4 | display: flex;
5 | flex-direction: column;
6 | height: 100px;
7 | justify-content: center;
8 | left: 50%;
9 | position: absolute;
10 | top: 50%;
11 | transform: translate(-50%);
12 | width: 100px;
13 | font-weight: bold;
14 | font-size: 1.3rem;
15 | }
16 |
17 | .preloader {
18 | position: relative;
19 | width: 65px;
20 | margin-bottom: 20px;
21 | }
22 |
23 | .preloader span {
24 | position: absolute;
25 | display: block;
26 | bottom: 0;
27 | width: 9px;
28 | height: 5px;
29 | border-radius: 5px;
30 | background: #16202B;
31 | animation: preloader 2s infinite ease-in-out;
32 | }
33 |
34 | .preloader span:nth-child(2) {
35 | left: 11px;
36 | -webkit-animation-delay: 200ms;
37 | animation-delay: 200ms;
38 | }
39 |
40 | .preloader span:nth-child(3) {
41 | left: 22px;
42 | -webkit-animation-delay: 400ms;
43 | animation-delay: 400ms;
44 | }
45 |
46 | .preloader span:nth-child(4) {
47 | left: 33px;
48 | -webkit-animation-delay: 600ms;
49 | animation-delay: 600ms;
50 | }
51 |
52 | .preloader span:nth-child(5) {
53 | left: 44px;
54 | -webkit-animation-delay: 800ms;
55 | animation-delay: 800ms;
56 | }
57 |
58 | .preloader span:nth-child(6) {
59 | left: 55px;
60 | -webkit-animation-delay: 1000ms;
61 | animation-delay: 1000ms;
62 | }
63 |
64 | @keyframes preloader {
65 | 0% {
66 | height: 5px;
67 | -webkit-transform: translateY(0);
68 | transform: translateY(0);
69 | background: #f88738;
70 | }
71 | 25% {
72 | height: 30px;
73 | -webkit-transform: translateY(15px);
74 | transform: translateY(15px);
75 | background: #DC327B;
76 | }
77 | 50%,100% {
78 | height: 5px;
79 | -webkit-transform: translateY(0);
80 | transform: translateY(0);
81 | background: #16202B;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/components/Loading/index.jsx:
--------------------------------------------------------------------------------
1 | import Loading from './Loading';
2 |
3 | export default Loading;
4 |
--------------------------------------------------------------------------------
/src/components/Post/Post.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | import './Post.scss';
5 |
6 | const Post = ({ postInfo, userInfo }) => {
7 | const [like, setLike] = useState(false);
8 | const [follow, toggleFollow] = useState(false);
9 |
10 | const { comments, imageUrl } = postInfo;
11 |
12 | return (
13 |
14 | {userInfo && (
15 |
16 |
17 |
18 |

19 |
20 |
21 |
{userInfo.name}
22 |
23 |
24 |
33 |
34 | )}
35 |
36 |
37 |
38 |
39 |
40 | {userInfo && (
41 |
63 | )}
64 |
65 | );
66 | };
67 |
68 | export default Post;
69 |
--------------------------------------------------------------------------------
/src/components/Post/Post.scss:
--------------------------------------------------------------------------------
1 | .post {
2 | background-color: #08111E;
3 | display: flex;
4 | flex-direction: column;
5 | margin-bottom: 30px;
6 | overflow: hidden;
7 | }
8 |
9 | .post__header {
10 | align-items: center;
11 | display: flex;
12 | justify-content: space-between;
13 | padding: 8px;
14 | }
15 |
16 | .post__header .user {
17 | display: flex;
18 | align-items: center;
19 | }
20 |
21 | .post__header .user__thumb {
22 | height: 40px;
23 | margin-right: 8px;
24 | border: 1px solid #FF991C;
25 | width: 40px;
26 | }
27 |
28 | .follow-btn {
29 | color: #858D93;
30 | font-weight: 600;
31 | font-size: 1.2rem;
32 |
33 | &.is-following {
34 | color: #ED2168;
35 | }
36 | }
37 |
38 | .post__header .user__name {
39 | font-size: 1.2rem;
40 | font-weight: bold;
41 | color: white;
42 | text-decoration: none;
43 | }
44 |
45 | .post__context {
46 | font-size: 1.4rem;
47 | }
48 |
49 | .post__figure {
50 | border-radius: 30px;
51 | height: auto;
52 | margin: 0;
53 | overflow: hidden;
54 | width: 100%;
55 | }
56 |
57 | .post__figure img {
58 | width: 100%;
59 | }
60 |
61 | .post__controls {
62 | width: 100%;
63 | display: flex;
64 | justify-content: flex-start;
65 | align-items: center;
66 | padding: 4px 0;
67 | }
68 |
69 | .post__control {
70 | color: #858D93;
71 | font-size: 2.4rem;
72 | margin-right: 5px;
73 | position: relative;
74 | }
75 |
76 | .fas.fa-heart {
77 | color: #ED2168;
78 | }
79 |
80 | .post__status {
81 | display: flex;
82 | padding: 0;
83 | line-height: 1.4em;
84 |
85 | span {
86 | color: #858D93;
87 | }
88 | }
89 |
90 | .post__status .user {
91 | display: flex;
92 | justify-content: flex-start;
93 | align-items: center;
94 | }
95 |
96 | .post__status .user span {
97 | font-size: 1.2rem;
98 | }
99 |
100 | .post__status .user__thumb {
101 | height: 30px;
102 | width: 30px;
103 | margin-right: 8px;
104 | }
105 |
--------------------------------------------------------------------------------
/src/components/Post/index.jsx:
--------------------------------------------------------------------------------
1 | import Post from './Post';
2 |
3 | export default Post;
4 |
--------------------------------------------------------------------------------
/src/components/Story/Story.jsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useState } from "react";
2 |
3 | import { Link } from 'react-router-dom';
4 |
5 | import './Story.scss';
6 |
7 | const Story = ({ story, user, handleClose }) => {
8 | const [metadata, setMetadata] = useState(null);
9 | const [currentTime, setCurrentTime] = useState(null);
10 |
11 | const updateProgress = useCallback(
12 | () => {
13 | if (metadata?.duration !== null && currentTime !== null) {
14 | const elapsedTime = ((currentTime / metadata.duration) * 100);
15 |
16 | return `${elapsedTime.toFixed(2)}%`;
17 | }
18 |
19 | return '0%';
20 | }, [metadata, currentTime]);
21 |
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |

29 |
30 |
31 |
{user.name}
32 |
33 |
34 |
40 |
41 |
42 |
48 |
49 |
50 | {story.videoUrl && (
51 |
52 |
53 |
69 |
70 | )}
71 |
72 | );
73 | };
74 |
75 | export default Story;
76 |
--------------------------------------------------------------------------------
/src/components/Story/Story.scss:
--------------------------------------------------------------------------------
1 | .story {
2 | background-color: #08111E;
3 | display: flex;
4 | flex-direction: column;
5 | height: 100vh;
6 | left: 0;
7 | position: fixed;
8 | top: 0;
9 | width: 100%;
10 | z-index: 9999;
11 | }
12 |
13 | .story__header {
14 | overflow: hidden;
15 | padding: 16px;
16 | align-items: center;
17 | display: flex;
18 | justify-content: space-between;
19 | }
20 |
21 | .story__progress {
22 | background-color: rgba(255, 255, 255, .05);
23 | border-radius: 4px;
24 | height: 4px;
25 | overflow: hidden;
26 | width: 100%;
27 | padding: 1px;
28 | }
29 |
30 | .story__progress__elapsed {
31 | transition: width ease-in-out 0.05s;
32 | width: 0;
33 | height: 1px;
34 | background: linear-gradient(to right, #FF991C 0%, #D51589 100%);
35 | }
36 |
37 | .story__close {
38 | font-size: 1.6rem;
39 | color: #858d93;
40 | }
41 |
42 | .story__header .user {
43 | display: flex;
44 | align-items: center;
45 | }
46 |
47 | .story__header .user__thumb {
48 | height: 40px;
49 | margin-right: 8px;
50 | border: 1px solid #FF991C;
51 | width: 40px;
52 | }
53 |
54 | .story__header .user__name {
55 | font-size: 1.2rem;
56 | font-weight: bold;
57 | color: white;
58 | text-decoration: none;
59 | }
60 |
61 | .story__video__wrapper {
62 | background-color: black;
63 | height: calc(100vh - 104px);
64 | overflow: hidden;
65 | border: 5px solid #16202b;
66 | padding: 0;
67 | border-radius: 30px;
68 | position: relative;
69 | width: 100%;
70 | margin: 16px 0;
71 | };
72 |
73 | .video-player {
74 | position: absolute;
75 | width: 100%;
76 | max-width: 100%;
77 | height: 100%;
78 | }
79 |
--------------------------------------------------------------------------------
/src/components/Story/index.jsx:
--------------------------------------------------------------------------------
1 | import Story from './Story';
2 |
3 | export default Story;
4 |
--------------------------------------------------------------------------------
/src/components/SuccessMessage/SuccessMessage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Link } from 'react-router-dom';
4 |
5 | import './SuccessMessage.scss';
6 |
7 | const SuccessMessage = () => (
8 |
9 |
40 |
41 |
Usuário Cadastrado!
42 |
43 |
44 |
Visite a página users para visualizar o novo usuário
45 |
46 |
47 | );
48 |
49 | export default SuccessMessage;
50 |
--------------------------------------------------------------------------------
/src/components/SuccessMessage/SuccessMessage.scss:
--------------------------------------------------------------------------------
1 | .success-message {
2 | align-items: center;
3 | background-color: #4caf50;
4 | border-radius: 30px;
5 | display: flex;
6 | flex-direction: column;
7 | height: 300px;
8 | justify-content: center;
9 | left: 50%;
10 | margin: auto;
11 | max-width: 400px;
12 | overflow: hidden;
13 | padding: 20px;
14 | position: fixed;
15 | text-align: center;
16 | top: 50%;
17 | transform: translate(-50%,-50%);
18 | width: 80%;
19 | z-index: 2;
20 |
21 | &__title {
22 | font-size: 2.4rem;
23 | line-height: 1;
24 | }
25 |
26 | &__content {
27 | font-size: 1.4rem;
28 | line-height: 1;
29 |
30 | a {
31 | color: #08111e;
32 | text-decoration: underline;
33 | }
34 | }
35 | }
36 |
37 | .successAnimationCircle {
38 | stroke-dasharray: 151px;
39 | stroke: #FFF;
40 | }
41 |
42 | .successAnimationCheck {
43 | stroke-dasharray: 36px 36px;
44 | stroke: #FFF;
45 | }
46 |
47 | .successAnimationResult {
48 | fill: #FFF;
49 | opacity: 0;
50 | }
51 |
52 | .successAnimation.animated {
53 | -webkit-animation: 1s ease-out 0s 1 both scaleAnimation;
54 | animation: 1s ease-out 0s 1 both scaleAnimation;
55 | }
56 | .successAnimation.animated .successAnimationCircle {
57 | -webkit-animation: 1s cubic-bezier(0.77, 0, 0.175, 1) 0s 1 both drawCircle, 0.3s linear 0.9s 1 both fadeOut;
58 | animation: 1s cubic-bezier(0.77, 0, 0.175, 1) 0s 1 both drawCircle, 0.3s linear 0.9s 1 both fadeOut;
59 | }
60 | .successAnimation.animated .successAnimationCheck {
61 | -webkit-animation: 1s cubic-bezier(0.77, 0, 0.175, 1) 0s 1 both drawCheck, 0.3s linear 0.9s 1 both fadeOut;
62 | animation: 1s cubic-bezier(0.77, 0, 0.175, 1) 0s 1 both drawCheck, 0.3s linear 0.9s 1 both fadeOut;
63 | }
64 | .successAnimation.animated .successAnimationResult {
65 | -webkit-animation: 0.3s linear 0.9s both fadeIn;
66 | animation: 0.3s linear 0.9s both fadeIn;
67 | }
68 |
69 | @keyframes scaleAnimation {
70 | 0% {
71 | opacity: 0;
72 | -webkit-transform: scale(1.5);
73 | transform: scale(1.5);
74 | }
75 | 100% {
76 | opacity: 1;
77 | -webkit-transform: scale(1);
78 | transform: scale(1);
79 | }
80 | }
81 |
82 | @keyframes drawCircle {
83 | 0% {
84 | stroke-dashoffset: 151px;
85 | }
86 | 100% {
87 | stroke-dashoffset: 0;
88 | }
89 | }
90 |
91 | @keyframes drawCheck {
92 | 0% {
93 | stroke-dashoffset: 36px;
94 | }
95 | 100% {
96 | stroke-dashoffset: 0;
97 | }
98 | }
99 |
100 | @keyframes fadeOut {
101 | 0% {
102 | opacity: 1;
103 | }
104 | 100% {
105 | opacity: 0;
106 | }
107 | }
108 |
109 | @keyframes fadeIn {
110 | 0% {
111 | opacity: 0;
112 | }
113 | 100% {
114 | opacity: 1;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/components/SuccessMessage/index.jsx:
--------------------------------------------------------------------------------
1 | import SuccessMessage from './SuccessMessage';
2 |
3 | export default SuccessMessage;
4 |
--------------------------------------------------------------------------------
/src/components/Topbar/Topbar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Link } from 'react-router-dom';
4 |
5 | import { ReactComponent as LogoSvg } from '../../assets/img/instagram-logo.svg';
6 |
7 | import './Topbar.scss';
8 |
9 | const Topbar = () => (
10 |
33 | );
34 |
35 | export default Topbar;
36 |
--------------------------------------------------------------------------------
/src/components/Topbar/Topbar.scss:
--------------------------------------------------------------------------------
1 | .topbar {
2 | backface-visibility: hidden;
3 | background-color: #16202B;
4 | left: 0;
5 | padding: 8px 16px;
6 | position: fixed;
7 | top: 0;
8 | width: 100%;
9 | z-index: 9000;
10 | }
11 |
12 | .topbar .container {
13 | display: flex;
14 | justify-content: space-between;
15 | }
16 |
17 | .topbar__logo {
18 | display: inline-block;
19 | height: 40px;
20 | }
21 |
22 | .topbar__logo svg {
23 | height: 100%;
24 | }
25 |
26 | .topbar__group {
27 | display: flex;
28 | justify-content: flex-end;
29 | }
30 |
31 | .topbar__icon,
32 | .topbar__icon a {
33 | align-items: center;
34 | color: #858D93;
35 | display: flex;
36 | flex-direction: column;
37 | font-size: 1.6rem;
38 | font-weight: normal;
39 | justify-content: center;
40 | margin: auto 0.5rem;
41 | transition: all 0.2s ease-in-out;
42 |
43 | span {
44 | display: none;
45 | }
46 | }
47 |
48 | .topbar__icon {
49 | a {
50 | &:hover {
51 | color: white;
52 | }
53 | }
54 | }
55 |
56 | @media screen and (min-width: 728px) {
57 | .topbar__icon,
58 | .topbar__icon a {
59 | margin: auto 0.5rem;
60 |
61 | span {
62 | display: inline-block;
63 | font-size: 1.1rem;
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/components/Topbar/index.jsx:
--------------------------------------------------------------------------------
1 | import Topbar from './Topbar';
2 |
3 | export default Topbar;
4 |
--------------------------------------------------------------------------------
/src/components/User/User.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Link } from 'react-router-dom';
4 |
5 | const User = ({ infoUser }) => {
6 | const {avatar, name, username, } = infoUser;
7 |
8 | return (
9 |
10 |
11 |
12 |
13 | {avatar
14 | ?

15 | :

16 | }
17 |
18 |
19 | {name}
20 |
21 |
22 |
23 | )
24 | };
25 |
26 | export default User;
27 |
--------------------------------------------------------------------------------
/src/components/User/index.jsx:
--------------------------------------------------------------------------------
1 | import User from './User';
2 |
3 | export default User;
4 |
--------------------------------------------------------------------------------
/src/containers/App/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter } from 'react-router-dom';
3 |
4 | import Topbar from '../../components/Topbar';
5 |
6 | import Routes from '../../routes';
7 |
8 | import './App.scss';
9 |
10 | const App = () => (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 |
20 | export default App;
21 |
--------------------------------------------------------------------------------
/src/containers/App/App.scss:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Open+Sans:400,700&display=swap');
2 |
3 | *,
4 | *:after,
5 | *:before {
6 | box-sizing: border-box;
7 | }
8 |
9 | html {
10 | font-size: 10px;
11 | }
12 |
13 | body {
14 | padding-top: 56px;
15 | font-family: 'Open Sans', sans-serif;
16 | background-color: #08111E;
17 | color: #fff;
18 | }
19 |
20 | a {
21 | text-decoration: none;
22 | color: #fff;
23 | font-weight: bold;
24 | }
25 |
26 | button {
27 | background-color: transparent;
28 | border: none;
29 | box-shadow: none;
30 | outline: none;
31 | -webkit-tap-highlight-color: rgba(0,0,0,0);
32 | }
33 |
34 | .container {
35 | margin: 0 auto;
36 | position: relative;
37 | width: 100%;
38 | }
39 |
40 | @media screen and (min-width: 728px) {
41 | .container {
42 | width: 600px;
43 | }
44 | }
45 |
46 | .user__thumb {
47 | display: flex;
48 | border-radius: 100%;
49 | overflow: hidden;
50 | }
51 |
52 | .user__thumb__wrapper {
53 | border-radius: 100%;
54 | border: 2px solid #16202B;
55 | display: inline-block;
56 | overflow: hidden;
57 | }
58 |
59 | .user__thumb img {
60 | height: 100%;
61 | width: 100%;
62 | }
63 |
64 | .user__thumb--hasNew {
65 | background: linear-gradient(45deg, #D51589 0%, #FF991C 100%);
66 | }
67 |
--------------------------------------------------------------------------------
/src/containers/App/index.jsx:
--------------------------------------------------------------------------------
1 | import App from './App';
2 |
3 | export default App;
4 |
--------------------------------------------------------------------------------
/src/containers/Posts/Posts.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Post from '../../components/Post';
4 |
5 | const Posts = ({ posts, getUserHandler }) => (
6 |
7 |
8 | { posts.length > 0 && posts.map((post) => (
9 |
14 | ))
15 | }
16 |
17 |
18 | );
19 |
20 | export default Posts;
21 |
--------------------------------------------------------------------------------
/src/containers/Posts/index.jsx:
--------------------------------------------------------------------------------
1 | import Posts from './Posts';
2 |
3 | export default Posts;
4 |
--------------------------------------------------------------------------------
/src/containers/Stories/Stories.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 |
3 | import Story from '../../components/Story';
4 |
5 | import './Stories.scss';
6 |
7 | const Stories = ({ stories, getUserHandler }) => {
8 | const [showStory, toggleShowStory] = useState(false);
9 | const [selectedStory, setSelectedHistory] = useState({});
10 | const [selectedProfile, setSelectedProfile] = useState({});
11 |
12 | const findStoryById = (id) => stories.find(story => story.id === id);
13 |
14 | const handleStory = (story) => {
15 | const foundStory = findStoryById(story.id);
16 | const profileData = getUserHandler(story.userId);
17 |
18 | setSelectedProfile(profileData);
19 | setSelectedHistory(foundStory);
20 | toggleShowStory(!showStory);
21 | };
22 |
23 | return (
24 |
25 |
26 |
27 | {stories.map((story, index) => {
28 | const profileData = getUserHandler(story.userId);
29 |
30 | return (
31 |
40 | )})
41 | }
42 |
43 |
44 |
45 | {showStory && (
46 | toggleShowStory(!showStory)}
50 | />
51 | )}
52 |
53 | );
54 | };
55 | export default Stories;
56 |
--------------------------------------------------------------------------------
/src/containers/Stories/Stories.scss:
--------------------------------------------------------------------------------
1 | .stories {
2 | background-color: #16202B;
3 | overflow-x: hidden;
4 | overflow-y: auto;
5 | padding: 16px;
6 | width: 100%;
7 | }
8 |
9 | .stories .container {
10 | align-items: center;
11 | display: flex;
12 | justify-content: flex-start;
13 | margin: 0 auto;
14 | }
15 |
16 | .stories .user__thumb {
17 | align-items: center;
18 | display: flex;
19 | flex-direction: column;
20 | border: none;
21 | height: 72px;
22 | justify-content: center;
23 | margin: 0 5px;
24 | padding: 0;
25 | width: 72px;
26 | }
27 |
28 | .stories .user__thumb__wrapper {
29 | height: 65px;
30 | width: 65px;
31 | }
32 |
--------------------------------------------------------------------------------
/src/containers/Stories/index.jsx:
--------------------------------------------------------------------------------
1 | import Stories from './Stories';
2 |
3 | export default Stories;
4 |
--------------------------------------------------------------------------------
/src/containers/UserForm/UserForm.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | import SuccessMessage from '../../components/SuccessMessage';
4 |
5 | import './UserForm.scss';
6 |
7 | const UserForm = () => {
8 | const [name, setName] = useState('John Doe');
9 | const [avatar, setAvatar] = useState('');
10 | const [username, setUsername] = useState('johndoe');
11 | const [email, setEmail] = useState('johndoe@gmail.com');
12 | const [submit, setSubmit] = useState(false);
13 |
14 | const handleSetName = (event) => {
15 | const { value } = event.target;
16 |
17 | setName(value);
18 | };
19 |
20 | const handleSetAvatar = (event) => {
21 | const { value } = event.target;
22 |
23 | setAvatar(value);
24 | };
25 |
26 | const handleSetUserName = (event) => {
27 | const { value } = event.target;
28 |
29 | setUsername(value);
30 | };
31 |
32 | const handleSetEmail = (event) => {
33 | const { value } = event.target;
34 |
35 | setEmail(value);
36 | };
37 |
38 | const handleAddUser = (event) => {
39 | event.preventDefault();
40 |
41 | const postObject = JSON.stringify({
42 | name,
43 | avatar,
44 | username,
45 | email,
46 | });
47 |
48 | fetch('https://5e7d0266a917d70016684219.mockapi.io/api/v1/users', {
49 | method: 'POST',
50 | headers: {
51 | 'Content-Type': 'application/json'
52 | },
53 | body: postObject
54 | }).then(() => setSubmit(true));
55 | };
56 |
57 | return (
58 |
59 |
60 |
61 |
62 |
63 |
64 | {avatar
65 | ?

66 | :

67 | }
68 |
69 |
70 | {name && (
71 |
72 | {name}
73 | @{username}
74 |
75 | )}
76 |
77 |
78 |
79 |
80 |
81 |
124 |
125 | {submit && ()}
126 |
127 | );
128 | };
129 |
130 | export default UserForm;
131 |
--------------------------------------------------------------------------------
/src/containers/UserForm/UserForm.scss:
--------------------------------------------------------------------------------
1 | .post__form {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: center;
5 | padding: 16px;
6 |
7 | @media screen and (min-width: 728px) {
8 | padding: 40px;
9 | }
10 |
11 | .post__form__wrapper {
12 | width: 100%;
13 | margin: 0 auto;
14 | display: flex;
15 | flex-direction: column;
16 | justify-content: center;
17 |
18 | @media screen and (min-width: 728px) {
19 | width: 80%;
20 | }
21 |
22 | button {
23 | background: linear-gradient(45deg, #D51589 0%, #FF991C 100%);
24 | color: white;
25 | font-size: 2rem;
26 | font-weight: bold;
27 | margin: 0 auto;
28 | padding: 10px 20px;
29 | border-radius: 5px;
30 | }
31 | }
32 |
33 | label {
34 | display: block;
35 | color: #858D93;
36 | font-size: 1.2rem;
37 | font-weight: normal;
38 | margin-bottom: 5px;
39 | }
40 |
41 | input {
42 | background-color: rgba(255, 255, 255, 0.02);
43 | border-radius: 5px;
44 | border: 1px solid #858D93;
45 | color: white;
46 | display: block;
47 | font-size: 16px;
48 | font-weight: bold;
49 | margin-bottom: 20px;
50 | outline: none;
51 | padding: 8px;
52 | width: 100%;
53 | transition: all ease-in-out .2s;
54 |
55 | &::placeholder {
56 | font-weight: normal;
57 | }
58 |
59 | &:focus {
60 | background-color: rgba(255, 255, 255, 0.1);
61 | border: 1px solid #F06B41;
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/containers/UserForm/index.jsx:
--------------------------------------------------------------------------------
1 | import UserForm from './UserForm';
2 |
3 | export default UserForm;
4 |
--------------------------------------------------------------------------------
/src/containers/UserPosts/UserPosts.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Post from '../../components/Post';
4 |
5 | import './UserPosts.scss';
6 |
7 | const UserPosts = ({ posts }) => (
8 |
9 |
10 | { posts.length > 0
11 | ? posts.map((post) => (
12 |
16 | ))
17 | : (
18 |
19 | Não há publicações deste usuário
20 | 😥
21 |
22 | )
23 | }
24 |
25 |
26 | );
27 |
28 | export default UserPosts;
29 |
--------------------------------------------------------------------------------
/src/containers/UserPosts/UserPosts.scss:
--------------------------------------------------------------------------------
1 | .user-posts {
2 | display: flex;
3 | flex-direction: row;
4 | flex-wrap: wrap;
5 | padding: 16px;
6 | position: relative;
7 | justify-content: space-between;
8 |
9 | & .post {
10 | margin: 6px;
11 | width: calc((100% / 2) - 12px);
12 |
13 | .post__figure {
14 | border-radius: 10px;
15 | }
16 | }
17 | }
18 |
19 | .no-posts {
20 | align-items: center;
21 | display: flex;
22 | flex-direction: column;
23 | font-size: 1.4rem;
24 | justify-content: center;
25 | width: 100%;
26 | padding: 40px;
27 |
28 | &__content {
29 | color: #858d93;
30 | }
31 |
32 | &__emoji {
33 | font-size: 5rem;
34 | }
35 | }
36 |
37 | @media screen and (min-width: 728px) {
38 | .user-posts {
39 | padding: 20px 16px 40px;
40 | justify-content: flex-start;
41 |
42 | & .post {
43 | width: calc((100% / 3) - 12px);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/containers/UserPosts/index.jsx:
--------------------------------------------------------------------------------
1 | import UserPosts from './UserPosts';
2 |
3 | export default UserPosts;
4 |
--------------------------------------------------------------------------------
/src/containers/UserProfile/UserProfile.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import './UserProfile.scss';
4 |
5 | const UserProfile = ({ avatar, name, username }) => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 | { avatar.length > 0
13 | ?

14 | :

15 | }
16 |
17 |
18 | {name && (
19 |
20 | {name}
21 | @{username}
22 |
23 | )}
24 |
25 |
26 |
27 |
28 | )
29 | };
30 |
31 | export default UserProfile;
32 |
--------------------------------------------------------------------------------
/src/containers/UserProfile/UserProfile.scss:
--------------------------------------------------------------------------------
1 | .profile {
2 | background-color: #16202B;
3 | display: flex;
4 | flex-direction: column;
5 | position: relative;
6 | }
7 |
8 | .profile-data {
9 | display: flex;
10 | flex-direction: column;
11 | justify-content: center;
12 | align-items: center;
13 | padding: 20px;
14 |
15 | .user {
16 | display: flex;
17 | flex-direction: column;
18 | justify-content: center;
19 | align-items: center;
20 | }
21 |
22 | .user__thumb {
23 | background: linear-gradient(45deg, #D51589 0%, #FF991C 100%);
24 | border: 2px solid transparent;
25 | height: 150px;
26 | width: 150px;
27 | margin: 0;
28 | }
29 |
30 | .user__name {
31 | display: flex;
32 | align-items: center;
33 | padding: 0;
34 | font-size: 2rem;
35 | line-height: 1;
36 | margin-bottom: 0;
37 | font-weight: bold;
38 | transition: all ease-in-out .2s;
39 | color: white;
40 |
41 | span {
42 | color: #858d93;
43 | font-weight: normal;
44 | font-size: 1.4rem;
45 |
46 | &:before {
47 | content: "-";
48 | margin: 0 10px;
49 | }
50 | }
51 | }
52 | }
53 |
54 | @media screen and (min-width: 728px) {
55 | .profile-data {
56 | padding: 40px 16px 20px;
57 |
58 | .user__thumb {
59 | height: 200px;
60 | width: 200px;
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/containers/UserProfile/index.jsx:
--------------------------------------------------------------------------------
1 | import UserProfile from './UserProfile';
2 |
3 | export default UserProfile;
4 |
--------------------------------------------------------------------------------
/src/containers/UsersList/UsersList.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import User from '../../components/User';
4 | import Loading from '../../components/Loading';
5 |
6 | import './UsersList.scss';
7 |
8 | const UersList = ({ users }) => {
9 | return (
10 |
11 | { users.length > 0
12 | ? users.map((user) => (
13 |
17 | ))
18 | :
19 | }
20 |
21 | )
22 | };
23 |
24 | export default UersList;
25 |
--------------------------------------------------------------------------------
/src/containers/UsersList/UsersList.scss:
--------------------------------------------------------------------------------
1 | .users-list {
2 | display: flex;
3 | flex-direction: row;
4 | flex-wrap: wrap;
5 | padding: 8px;
6 | position: relative;
7 | width: 100%;
8 | justify-content: center;
9 | align-items: center;
10 |
11 | .post {
12 | align-items: center;
13 | background-color: rgba(22, 32, 43, 1);
14 | border-radius: 6px;
15 | display: flex;
16 | flex-direction: column;
17 | justify-content: center;
18 | margin: 6px auto;
19 | overflow: hidden;
20 | padding: 0;
21 | transition: all ease-in-out .2s;
22 | width: calc((100% / 2) - 12px);
23 |
24 | &:hover {
25 | background-color: rgba(22, 32, 43, .6);
26 |
27 |
28 | .post__header .user__name {
29 | color: white;
30 | }
31 |
32 | .user__thumb {
33 | background: linear-gradient(45deg, #D51589 0%, #FF991C 100%);
34 | }
35 | }
36 | }
37 |
38 | @media screen and (min-width: 728px) {
39 | .users-list {
40 | padding: 40px 16px;
41 | }
42 |
43 | .post {
44 | width: calc((100% / 3) - 12px);
45 | }
46 | }
47 |
48 | .post__header,
49 | .post__header .user {
50 | align-items: center;
51 | display: flex;
52 | flex-direction: column;
53 | justify-content: center;
54 | padding: 0;
55 | width: 100%;
56 | }
57 |
58 | .post__header .user {
59 | padding: 15px;
60 | }
61 |
62 | .post__header .user__name {
63 | padding: 15px 0;
64 | font-size: 1.4rem;
65 | color: #858d93;
66 | transition: all ease-in-out .2s;
67 | }
68 |
69 | .user__thumb {
70 | background: linear-gradient(45deg, #08111e 0%, #08111e 100%);
71 | border: 2px solid transparent;
72 | height: 100px;
73 | margin: 0;
74 | transition: all ease-in-out .2s;
75 | width: 100px;
76 | }
77 | }
78 |
79 | @media screen and (min-width: 728px) {
80 | .users-list {
81 | padding: 16px 0;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/containers/UsersList/index.jsx:
--------------------------------------------------------------------------------
1 | import UsersList from './UsersList';
2 |
3 | export default UsersList;
4 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import * as serviceWorker from './serviceWorker';
5 |
6 | import App from './containers/App';
7 |
8 | ReactDOM.render(
9 | ,
10 | document.getElementById('root')
11 | );
12 |
13 | // If you want your app to work offline and load faster, you can change
14 | // unregister() to register() below. Note this comes with some pitfalls.
15 | // Learn more about service workers: https://bit.ly/CRA-PWA
16 | serviceWorker.unregister();
17 |
--------------------------------------------------------------------------------
/src/modules/enpoints.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/src/modules/enpoints.js
--------------------------------------------------------------------------------
/src/modules/formatter.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/src/modules/formatter.js
--------------------------------------------------------------------------------
/src/modules/request.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codenation-dev/react-instagram/2dc576d1dedf63cfd86c45ca5c2ce8371cdbb4d8/src/modules/request.js
--------------------------------------------------------------------------------
/src/routes/FeedRoute/FeedRoute.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | import Stories from '../../containers/Stories';
4 | import Loading from '../../components/Loading';
5 |
6 | import Posts from '../../containers/Posts';
7 |
8 | import './FeedRoute.scss';
9 |
10 | const FeedRoute = () => {
11 | const [users, setUsers] = useState([]);
12 | const [posts, setPosts] = useState([]);
13 | const [stories, setStories] = useState([]);
14 | const [usersFetched, setUsersFetched] = useState(0);
15 |
16 | const getUserPostById = (postUserId) => users.find(user => postUserId === user.id);
17 |
18 | useEffect(() => {
19 | fetch('https://5e7d0266a917d70016684219.mockapi.io/api/v1/users')
20 | .then((res) => res.json())
21 | .then(data => setUsers(data));
22 | }, []);
23 |
24 | useEffect(() => {
25 | if (usersFetched === users.length) {
26 | return;
27 | }
28 |
29 | fetch(`https://5e7d0266a917d70016684219.mockapi.io/api/v1/users/${users[usersFetched].id}/posts`)
30 | .then((res) => res.json())
31 | .then(data => {
32 | setPosts([...posts, ...data]);
33 | setUsersFetched(usersFetched + 1);
34 | });
35 |
36 | // eslint-disable-next-line react-hooks/exhaustive-deps
37 | }, [users, usersFetched]);
38 |
39 | useEffect(() => {
40 | fetch('https://5e7d0266a917d70016684219.mockapi.io/api/v1/stories')
41 | .then((res) => res.json())
42 | .then(data => {
43 | setStories(data);
44 | });
45 | }, [users]);
46 |
47 | return (
48 |
49 | {(users.length > 0 && stories.length > 0) && (
50 |
54 | )}
55 |
56 | {users.length !== usersFetched
57 | ? (
)
58 | : (
59 |
)
63 | }
64 |
65 | );
66 | };
67 |
68 | export default FeedRoute;
69 |
--------------------------------------------------------------------------------
/src/routes/FeedRoute/FeedRoute.scss:
--------------------------------------------------------------------------------
1 | .feed {
2 | display: flex;
3 | flex-direction: column;
4 | padding: 16px;
5 | position: relative;
6 | width: 100%;
7 | }
8 |
9 | @media screen and (min-width: 728px) {
10 | .feed {
11 | padding: 16px 0;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/routes/FeedRoute/index.jsx:
--------------------------------------------------------------------------------
1 | import FeedRoute from './FeedRoute';
2 |
3 | export default FeedRoute;
4 |
--------------------------------------------------------------------------------
/src/routes/NewUserRoute/NewUserRoute.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import UserForm from '../../containers/UserForm';
4 |
5 | const NewUserRoute = () => (
6 |
7 |
8 |
9 | );
10 |
11 | export default NewUserRoute;
12 |
--------------------------------------------------------------------------------
/src/routes/NewUserRoute/index.jsx:
--------------------------------------------------------------------------------
1 | import NewUserRoute from './NewUserRoute';
2 |
3 | export default NewUserRoute;
4 |
--------------------------------------------------------------------------------
/src/routes/ProfileRoute/ProfileRoute.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | import UserProfile from '../../containers/UserProfile';
4 | import UserPosts from '../../containers/UserPosts';
5 |
6 | import Loading from '../../components/Loading';
7 |
8 | const ProfileRoute = () => {
9 | const [name, setName] = useState('');
10 | const [id, setUserId] = useState('');
11 | const [avatar, setAvatar] = useState('');
12 | const [username, setUsername] = useState('');
13 | const [email, setEmail] = useState('');
14 | const [userPosts, setUserPosts] = useState([]);
15 | const [isLoading, setIsLoading] = useState(true);
16 |
17 | useEffect(
18 | () => {
19 | const { pathname } = window.location;
20 | const param = pathname.split("/")[2];
21 |
22 | fetch(`https://5e7d0266a917d70016684219.mockapi.io/api/v1/users?search=${param}`)
23 | .then(response => response.json())
24 | .then(profileData => {
25 | setAvatar(profileData[0].avatar);
26 | setEmail(profileData[0].email);
27 | setName(profileData[0].name);
28 | setUsername(profileData[0].username);
29 | setUserId(profileData[0].id);
30 | });
31 | }, []);
32 |
33 | useEffect(() => {
34 | if (id) {
35 | fetch(`https://5e7d0266a917d70016684219.mockapi.io/api/v1/users/${id}/posts`)
36 | .then(response => response.json())
37 | .then(posts => {
38 | setUserPosts(posts);
39 | setIsLoading(false)
40 | });
41 | }
42 | }, [id]);
43 |
44 | return (
45 |
46 |
52 |
53 | {isLoading
54 | ? ()
55 | :
56 | }
57 |
58 | );
59 | };
60 |
61 | export default ProfileRoute;
62 |
--------------------------------------------------------------------------------
/src/routes/ProfileRoute/index.jsx:
--------------------------------------------------------------------------------
1 | import ProfileRoute from './ProfileRoute';
2 |
3 | export default ProfileRoute;
4 |
--------------------------------------------------------------------------------
/src/routes/UsersRoute/UsersRoute.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | import UsersList from '../../containers/UsersList/UsersList';
4 |
5 | const UsersRoute = () => {
6 | const [users, setUsers] = useState([]);
7 |
8 | useEffect(() => {
9 | fetch('https://5e7d0266a917d70016684219.mockapi.io/api/v1/users')
10 | .then((res) => res.json())
11 | .then(data => setUsers(data));
12 | }, []);
13 |
14 | return (
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default UsersRoute;
22 |
--------------------------------------------------------------------------------
/src/routes/UsersRoute/index.jsx:
--------------------------------------------------------------------------------
1 | import UsersRoute from './UsersRoute';
2 |
3 | export default UsersRoute;
4 |
--------------------------------------------------------------------------------
/src/routes/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, Switch } from 'react-router-dom';
3 |
4 | import FeedRoute from './FeedRoute';
5 | import UsersRoute from './UsersRoute';
6 | import ProfileRoute from './ProfileRoute';
7 | import NewUserRoute from './NewUserRoute';
8 |
9 | const Routes = () => (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 |
29 | export default Routes;
30 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------