├── public ├── favicon.ico ├── manifest.json └── index.html ├── React_MST_GraphQL_Logo.jpg ├── React_MST_GraphQL_Logo.psd ├── .gitignore ├── src ├── components │ ├── header │ │ ├── avatar.js │ │ └── header.js │ ├── repositories │ │ ├── respositories.js │ │ └── repository.js │ ├── grid │ │ └── grid.js │ └── loading │ │ └── loading.js ├── index.js ├── services │ └── graphql.service.js ├── queries │ └── viewer.query.js ├── containers │ └── app │ │ └── app.js ├── stylesheets │ └── common.css ├── logo.svg ├── stores │ └── github.js └── registerServiceWorker.js ├── package.json ├── .github └── FUNDING.yml └── README.md /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvcasillas/react-mobx-state-tree/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /React_MST_GraphQL_Logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvcasillas/react-mobx-state-tree/HEAD/React_MST_GraphQL_Logo.jpg -------------------------------------------------------------------------------- /React_MST_GraphQL_Logo.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvcasillas/react-mobx-state-tree/HEAD/React_MST_GraphQL_Logo.psd -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # firebase 24 | /firebase 25 | -------------------------------------------------------------------------------- /src/components/header/avatar.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Avatar = styled.div` 4 | width: 120px; 5 | height: 120px; 6 | border-radius: 50%; 7 | background-image: url(${({ picture }) => picture}); 8 | background-position: center; 9 | background-size: cover; 10 | border: 4px solid #ffffff; 11 | margin: 40px auto; 12 | box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.2); 13 | `; 14 | 15 | export default Avatar; 16 | -------------------------------------------------------------------------------- /src/components/repositories/respositories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | import Repository from './repository'; 5 | 6 | const Wrapper = styled.div` 7 | width: 100%; 8 | max-width: 100%; 9 | margin: 0 auto; 10 | display: flex; 11 | flex-direction: row; 12 | flex-wrap: wrap; 13 | `; 14 | 15 | const Repositories = ({ repos }) => 16 | 17 | {repos.map((repo, i) => )} 18 | ; 19 | 20 | export default Repositories; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-mobx-state-tree", 3 | "version": "0.2.0", 4 | "private": true, 5 | "dependencies": { 6 | "apollo-boost": "^0.1.4", 7 | "graphql": "^0.13.2", 8 | "mobx": "^4.2.0", 9 | "mobx-react": "^5.0.0", 10 | "mobx-state-tree": "^2.0.4", 11 | "react": "^16.3.2", 12 | "react-dom": "^16.3.2", 13 | "react-scripts": "1.1.4", 14 | "styled-components": "^3.2.6" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test --env=jsdom", 20 | "eject": "react-scripts eject" 21 | } 22 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Provider } from 'mobx-react'; 4 | import GithubStore from './stores/github'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | import App from './containers/app/app'; 7 | 8 | import './stylesheets/common.css'; 9 | 10 | const githubStore = GithubStore.create(); 11 | githubStore.fetchFromGithub(); 12 | 13 | const store = { 14 | github: githubStore 15 | }; 16 | 17 | const router = ( 18 | 19 |
20 | 21 |
22 |
23 | ); 24 | 25 | render(router, document.getElementById('root')); 26 | registerServiceWorker(); 27 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: alexvcasillas 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /src/services/graphql.service.js: -------------------------------------------------------------------------------- 1 | import ApolloClient, { gql } from 'apollo-boost'; 2 | 3 | const githubToken = 'your_token_here'; 4 | 5 | const client = new ApolloClient({ 6 | uri: 'https://api.github.com/graphql', 7 | fetchOptions: { 8 | credentials: 'include' 9 | }, 10 | request: (operation) => { 11 | operation.setContext({ 12 | headers: { 13 | authorization: `Bearer ${githubToken}` 14 | } 15 | }); 16 | }, 17 | onError: ({ graphQLErrors, networkError }) => { 18 | if (graphQLErrors) return console.error('GrahQL Errors:', graphQLErrors); 19 | if (networkError) return console.error('Network Error: ', networkError); 20 | } 21 | }); 22 | 23 | export { client, gql }; -------------------------------------------------------------------------------- /src/queries/viewer.query.js: -------------------------------------------------------------------------------- 1 | import { gql } from '../services/graphql.service'; 2 | 3 | export const viewerQuery = gql` 4 | query { 5 | viewer { 6 | avatarUrl 7 | bio 8 | login 9 | name 10 | followers { 11 | totalCount 12 | } 13 | following { 14 | totalCount 15 | } 16 | websiteUrl 17 | url 18 | repositories(first: 100) { 19 | totalCount 20 | nodes { 21 | name 22 | description 23 | url 24 | homepageUrl 25 | isFork 26 | createdAt 27 | updatedAt 28 | stargazers { 29 | totalCount 30 | } 31 | } 32 | } 33 | } 34 | } 35 | `; -------------------------------------------------------------------------------- /src/components/grid/grid.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Grid = styled.div` 4 | display: flex; 5 | flex-direction: row; 6 | flex-wrap: nowrap; 7 | margin-top: ${({ margins }) => (margins && margins.top ? margins.top : 0)}px; 8 | margin-bottom: ${({ margins }) => 9 | margins && margins.bottom ? margins.bottom : 0}px; 10 | margin-left: ${({ margins }) => 11 | margins && margins.left ? margins.left : 0}px; 12 | margin-right: ${({ margins }) => 13 | margins && margins.right ? margins.right : 0}px; 14 | `; 15 | 16 | const Column = styled.div` 17 | width: ${({ size }) => 18 | size === 2 ? '50%' : size === 3 ? '33.333%' : size === 4 ? '25%' : '100%'}; 19 | margin-right: ${({ margin }) => 20 | margin && margin.right ? margin.right : '10'}px; 21 | &:last-child { 22 | margin-right: 0; 23 | } 24 | `; 25 | 26 | export default Grid; 27 | export { Column }; 28 | -------------------------------------------------------------------------------- /src/containers/app/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { inject, observer } from 'mobx-react'; 3 | import styled from 'styled-components'; 4 | 5 | import Loading from '../../components/loading/loading'; 6 | import Header from '../../components/header/header'; 7 | import Repositories from '../../components/repositories/respositories'; 8 | 9 | const defaultName = 'Alex Casillas'; 10 | const defaultAvatar = 11 | 'https://avatars0.githubusercontent.com/u/9496960?v=4&s=460'; 12 | 13 | const Wrapper = styled.div` 14 | width: 1200px; 15 | max-width: 100%; 16 | margin: 0 auto; 17 | margin-bottom: 50px; 18 | `; 19 | 20 | const App = ({ github }) => 21 | github.fetchingData 22 | ? 23 | : 24 |
30 | {github.repos.length ? : null} 31 | ; 32 | 33 | export default inject('github')(observer(App)); 34 | -------------------------------------------------------------------------------- /src/components/loading/loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled, { keyframes } from 'styled-components'; 3 | 4 | const foregroundCircle = keyframes` 5 | 0% { 6 | ransform: scale(0.3) rotate(0deg); 7 | } 8 | 12.5% { 9 | transform: scale(0.3) rotate(180deg); 10 | } 11 | 25%, 50% { 12 | opacity: 1; 13 | } 14 | 50% { 15 | transform: scale(1) rotate(720deg); 16 | } 17 | 100% { 18 | transform: scale(0.3) rotate(1800deg); 19 | opacity: 0.5; 20 | } 21 | `; 22 | 23 | const backgroundCircle = keyframes` 24 | 12.5% { 25 | transform: scale(0.3); 26 | } 27 | 90%, 100% { 28 | transform: scale(2); 29 | opacity: 0; 30 | } 31 | `; 32 | 33 | const Loader = styled.div` 34 | width: 100px; 35 | height: 100px; 36 | position: relative; 37 | margin: auto; 38 | 39 | &:before, 40 | &:after { 41 | content: ''; 42 | position: absolute; 43 | border-radius: 50%; 44 | width: 100%; 45 | height: 100%; 46 | top: 0; 47 | left: 0; 48 | 49 | animation-duration: 3s; 50 | 51 | animation-timing-function: linear; 52 | 53 | animation-iteration-count: infinite; 54 | } 55 | 56 | &:before { 57 | top: -2px; 58 | left: -2px; 59 | border-style: solid; 60 | border-width: 3px 3px 3px 0; 61 | border-color: #fff transparent transparent; 62 | transform: scale(0.3) rotate(0deg); 63 | opacity: 0.5; 64 | animation-name: ${foregroundCircle}; 65 | } 66 | 67 | &:after { 68 | background: #fff; 69 | opacity: 0.5; 70 | transform: scale(0); 71 | animation-name: ${backgroundCircle}; 72 | } 73 | `; 74 | 75 | const Wrapper = styled.div` 76 | width: 100%; 77 | height: 100%; 78 | display: flex; 79 | flex-direction: column; 80 | align-items: center; 81 | justify-content: center; 82 | position: absolute; 83 | top: 0; 84 | left: 0; 85 | background-image: linear-gradient(to bottom left, #48c6ef 0%, #6f86d6 100%); 86 | `; 87 | 88 | const Loading = () => 89 | 90 | 91 | ; 92 | 93 | export default Loading; 94 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 28 | Alex Casillas - @alexvcasillas 29 | 30 | 31 | 32 | 35 |
36 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/stylesheets/common.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | a, 6 | abbr, 7 | acronym, 8 | address, 9 | applet, 10 | article, 11 | aside, 12 | audio, 13 | b, 14 | big, 15 | blockquote, 16 | body, 17 | canvas, 18 | caption, 19 | center, 20 | cite, 21 | code, 22 | dd, 23 | del, 24 | details, 25 | dfn, 26 | div, 27 | dl, 28 | dt, 29 | em, 30 | embed, 31 | fieldset, 32 | figcaption, 33 | figure, 34 | footer, 35 | form, 36 | h1, 37 | h2, 38 | h3, 39 | h4, 40 | h5, 41 | h6, 42 | header, 43 | hgroup, 44 | html, 45 | i, 46 | iframe, 47 | img, 48 | ins, 49 | kbd, 50 | label, 51 | legend, 52 | li, 53 | mark, 54 | menu, 55 | nav, 56 | object, 57 | ol, 58 | output, 59 | p, 60 | pre, 61 | q, 62 | ruby, 63 | s, 64 | samp, 65 | section, 66 | small, 67 | span, 68 | strike, 69 | strong, 70 | sub, 71 | summary, 72 | sup, 73 | table, 74 | tbody, 75 | td, 76 | tfoot, 77 | th, 78 | thead, 79 | time, 80 | tr, 81 | tt, 82 | u, 83 | ul, 84 | var, 85 | video { 86 | margin: 0; 87 | padding: 0; 88 | border: 0; 89 | font-size: 100%; 90 | font: inherit; 91 | vertical-align: baseline; 92 | } 93 | 94 | /* HTML5 display-role reset for older browsers */ 95 | article, 96 | aside, 97 | details, 98 | figcaption, 99 | figure, 100 | footer, 101 | header, 102 | hgroup, 103 | menu, 104 | nav, 105 | section { 106 | display: block; 107 | } 108 | 109 | body { 110 | line-height: 1; 111 | } 112 | 113 | ol, 114 | ul { 115 | list-style: none; 116 | } 117 | 118 | blockquote, 119 | q { 120 | quotes: none; 121 | } 122 | 123 | blockquote:after, 124 | blockquote:before, 125 | q:after, 126 | q:before { 127 | content: ''; 128 | content: none; 129 | } 130 | 131 | table { 132 | border-collapse: collapse; 133 | border-spacing: 0; 134 | } 135 | 136 | .pointer { 137 | cursor: pointer; 138 | } 139 | 140 | * { 141 | margin: 0; 142 | padding: 0; 143 | box-sizing: border-box; 144 | } 145 | 146 | body { 147 | font-family: 'Open Sans', sans-serif; 148 | font-weight: 400; 149 | background-image: linear-gradient(to bottom left, #48c6ef 0%, #6f86d6 100%); 150 | color: white; 151 | } 152 | 153 | strong { 154 | font-weight: 600; 155 | } 156 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/stores/github.js: -------------------------------------------------------------------------------- 1 | import { types, flow } from 'mobx-state-tree'; 2 | import { client } from '../services/graphql.service'; 3 | import { viewerQuery } from '../queries/viewer.query'; 4 | 5 | function compareRepo(a, b) { 6 | const aUpdated = new Date(a.updatedAt); 7 | const bUpdated = new Date(b.updatedAt); 8 | if (aUpdated > bUpdated) { 9 | return -1; 10 | } 11 | if (aUpdated < bUpdated) { 12 | return 1; 13 | } 14 | return 0; 15 | } 16 | 17 | const UserModel = types 18 | .model('UserModel', { 19 | name: types.maybe(types.string), 20 | bio: types.maybe(types.string), 21 | avatar: types.maybe(types.string), 22 | followers: types.maybe(types.number), 23 | following: types.maybe(types.number) 24 | }) 25 | .views(self => ({})) 26 | .actions(self => ({})); 27 | 28 | const RepositoryModel = types 29 | .model('RepoModel', { 30 | name: types.string, 31 | description: types.maybe(types.string), 32 | url: types.maybe(types.string), 33 | homepageUrl: types.maybe(types.string), 34 | isFork: types.maybe(types.boolean), 35 | stargazers: types.optional(types.frozen, null), 36 | createdAt: types.maybe(types.string), 37 | updatedAt: types.maybe(types.string) 38 | }) 39 | .views(self => ({})) 40 | .actions(self => ({})); 41 | 42 | const GithubStore = types 43 | .model('GithubStore', { 44 | searchName: types.optional(types.string, ''), 45 | user: types.optional(types.maybe(UserModel), null), // Object with all the user data that comes from the Github API Fetch 46 | repos: types.optional(types.array(RepositoryModel), []), // Array of Repositories that comes from the Github API Fetch 47 | fetchingData: types.optional(types.boolean, false) 48 | }) 49 | .views(self => ({ 50 | get AmountOfRepos() { 51 | return this.repos.length; 52 | } 53 | })) 54 | .actions(self => { 55 | const fetchFromGithub = flow(function* () { 56 | self.fetchingData = true; 57 | const { data: { viewer } } = yield client.query({ 58 | query: viewerQuery, 59 | fetchPolicy: 'network-only' 60 | }); 61 | self.user = UserModel.create({ 62 | name: viewer.name, 63 | bio: viewer.bio, 64 | avatar: viewer.avatarUrl, 65 | followers: viewer.followers.totalCount, 66 | following: viewer.following.totalCount 67 | }); 68 | let repoOrder = [...viewer.repositories.nodes]; 69 | repoOrder = repoOrder.sort(compareRepo); 70 | self.repos = repoOrder; 71 | self.fetchingData = false; 72 | }); 73 | return { fetchFromGithub }; 74 | }); 75 | 76 | export default GithubStore; 77 | -------------------------------------------------------------------------------- /src/components/repositories/repository.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const Wrapper = styled.div` 5 | width: 50%; 6 | padding: 20px; 7 | 8 | @media (max-width: 768px) { 9 | width: 100%; 10 | } 11 | `; 12 | 13 | const Card = styled.div` 14 | background-color: #ffffff; 15 | box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.2); 16 | color: #1e1e1e; 17 | border-radius: 4px; 18 | padding: 20px; 19 | `; 20 | 21 | const Title = styled.div` 22 | display: block; 23 | font-weight: 600; 24 | margin-bottom: 10px; 25 | max-width: 80%; 26 | white-space: nowrap; 27 | overflow: hidden; 28 | text-overflow: ellipsis; 29 | padding-bottom: 5px; 30 | margin-bottom: 10px; 31 | `; 32 | 33 | const Fork = styled.span` 34 | display: inline-block; 35 | margin-right: 10px; 36 | color: #979797; 37 | `; 38 | 39 | const Link = styled.a` 40 | display: inline-block; 41 | text-decoration: none; 42 | outline: none; 43 | color: #6f86d6; 44 | transition: color 0.3s ease-in-out; 45 | 46 | &:hover { 47 | text-decoration: none; 48 | outline: none; 49 | color: #48c6ef; 50 | } 51 | `; 52 | 53 | const Homepage = styled.a` 54 | max-width: 100%; 55 | display: inline-block; 56 | text-decoration: none; 57 | outline: none; 58 | color: #6f86d6; 59 | transition: color 0.3s ease-in-out; 60 | 61 | &:hover { 62 | text-decoration: none; 63 | outline: none; 64 | color: #48c6ef; 65 | } 66 | `; 67 | 68 | const Description = styled.div` 69 | line-height: 1.4; 70 | white-space: nowrap; 71 | overflow: hidden; 72 | text-overflow: ellipsis; 73 | text-decoration: none; 74 | `; 75 | 76 | const Block = styled.div` 77 | width: 100%; 78 | display: flex; 79 | flex-direction: row; 80 | margin-top: 10px; 81 | 82 | strong { 83 | margin-right: 5px; 84 | } 85 | `; 86 | 87 | const Repository = ({ repository }) => ( 88 | 89 | 90 | 91 | {repository.isFork ? <Fork>Forked</Fork> : ''} 92 | <Link href={repository.url} target="_blank" rel="noopener noreferrer"> 93 | {repository.name} 94 | </Link> 95 | 96 | {repository.description} 97 | 98 | Homepage: 99 | 100 | {repository.homepageUrl} 101 | 102 | 103 | 104 | 105 | 106 | 🌟 107 | {' '} 108 | Stargrazzers:{' '} 109 | {' '} 110 | {repository.stargazers.totalCount} 111 | 112 | 113 | Created at: {' '} 114 | {new Date(repository.createdAt).toLocaleString()} 115 | 116 | 117 | Last update at: {' '} 118 | {new Date(repository.updatedAt).toLocaleString()} 119 | 120 | 121 | 122 | ); 123 | 124 | export default Repository; 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![React + MobX State Tree & GraphQL Logo](https://raw.githubusercontent.com/alexvcasillas/react-mobx-state-tree/master/React_MST_GraphQL_Logo.jpg) 2 | 3 | ### About React + MobX State Tree & GraphQL 4 | 5 | This project was initially built as a personal project to create my own website ([https://alexvcasillas.com](https://alexvcasillas.com)) to test the latest features of the awesome [MobX State Tree](https://github.com/mobxjs/mobx-state-tree) and [GraphQL](https://github.com/facebook/graphql). The React part of this project is based on the latest [Create React App](https://github.com/facebookincubator/create-react-app) stable release (check their docs for further knowledge). 6 | 7 | ### Purpose 8 | 9 | The purpose of this project is only intended for learning. It's not intended to be a commercial product or any other related kind of stuff. This is Open Source so you should feel free to use it and modify it as you wanted. In fact, I encourage you to modify the structure or how I do things to fit your needs and the way you handle things, just set it to be comfortable to you. 10 | 11 | ### How To 12 | 13 | If you want to try this project the first thing you have to do is to **clone** this repository. How? Just go to your favourite terminal and execute the following command (assuming that you previously have **git** installed): `git clone https://github.com/alexvcasillas/react-mobx-state-tree.git`. By doing so, you'll have the repository cloned in your computer. The next step is to move inside by typing `cd react-mobx-state-tree`. Alright, we're in now! You can't run it at this point because you have no dependencies installed so, we need to start with that, run the following command: `npm install` or if you're a yarn user just `yarn`. 14 | This might take a while because it's downloading all the minimum requirements as local dependencies so you can run this project (they're just a few). When it's done, you're good to go, simply run the following command `npm start` or if you're a yarn user `yarn start` and you will see the development build process and when everything is done, your browser will open a new window/tab with this project up and running. 15 | 16 | If you want your Github profile to fill this project, you need to generate a Github's Personal Access Token (PAT) and go to `src/utils/graphql.js - line 10` and replace `const githubToken = 'your_token_here';` the content within single quotes with your Personal Access Token (PAT). Then just make the build and deploy it somewhere or just run it as development. 17 | 18 | You can customize everything with Styled Components! 19 | 20 | ### TLDR: How To 21 | 22 | 1. `git clone https://github.com/alexvcasillas/react-mobx-state-tree.git` 23 | 2. `cd react-mobx-state-tree` 24 | 3. `npm install` or `yarn` 25 | 4. `npm start` or `yarn start` 26 | 27 | ### Production Builds 28 | 29 | Because this project is based on [Create React App](https://github.com/facebookincubator/create-react-app), you have all the CRA available commands and the build process is delegated to them. Simply run `npm run build` or `yarn build` and the build process will being and, at the end of it, you'll have a `dist` folder with your SPA ready to be deployed as a static website at any host. 30 | 31 | ### Built with 32 | 33 | This project is being used in: 34 | 35 | [https://alexvcasillas.com](https://alexvcasillas.com) 36 | 37 | ### Contributions 38 | 39 | I'm always looking to improve this project so, if you feel like you can contribute to it to improve any of it's features, just fork it and make a proper Pull Request. I'll be so glad to check it with you and merge it into the master project. 40 | 41 | ### Credits 42 | 43 | I want to thank the following people and organizations for creating all that this project is based on: 44 | 45 | [Michel Weststrate](https://github.com/mweststrate) for the incredible job creating MobX and MobX State Tree. 46 | 47 | [Mattia Manzati](https://github.com/mattiamanzati) for it's support with everything related to MobX State Tree. 48 | 49 | [Max Stoiber](https://github.com/mxstbr) for it's awesome Styled Components :nail_care: 50 | 51 | [Dan Abramov](https://github.com/gaearon) for creating Create React App (also all of the contributors to it!) 52 | 53 | [Apollo GraphQL Team](https://github.com/apollographql) for their awesome contribution with Apollo Client. 54 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (!isLocalhost) { 36 | // Is not local host. Just register service worker 37 | registerValidSW(swUrl); 38 | } else { 39 | // This is running on localhost. Lets check if a service worker still exists or not. 40 | checkValidServiceWorker(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/components/header/header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | import Grid, { Column } from '../grid/grid'; 5 | 6 | import Avatar from './avatar'; 7 | 8 | const Wrapper = styled.div` 9 | width: 600px; 10 | max-width: 80%; 11 | margin: 0 auto; 12 | margin-bottom: 40px; 13 | `; 14 | 15 | const Name = styled.div` 16 | text-align: center; 17 | margin-bottom: 20px; 18 | `; 19 | 20 | const Jump = styled.span` 21 | display: inline-block; 22 | transition: transform 0.2s; 23 | cursor: default; 24 | min-width: 20px; 25 | text-shadow: 3px 3px 0 rgba(0, 0, 0, 0.2); 26 | font-size: 50px; 27 | text-transform: uppercase; 28 | font-weight: 600; 29 | 30 | @media (max-width: 768px) { 31 | font-size: 20px; 32 | } 33 | 34 | &:hover { 35 | transform: translateY(-20px) rotate(10deg) scale(2); 36 | } 37 | `; 38 | 39 | const Title = styled.div` 40 | font-weight: 600; 41 | margin-bottom: 10px; 42 | `; 43 | 44 | const Amount = styled.div` 45 | font-size: 40px; 46 | font-family: 'Pacifico', sans-serif; 47 | color: ${({ count }) => (count && count >= 100 ? '#ffc600' : 'inherit')}; 48 | `; 49 | 50 | const FollowBox = styled.div` 51 | width: 100%; 52 | padding: 20px; 53 | background-color: #ffffff; 54 | box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.2); 55 | color: #1e1e1e; 56 | border-radius: 4px; 57 | text-align: center; 58 | `; 59 | 60 | const Social = styled.div`margin: 40px 0;`; 61 | 62 | const SVG = styled.svg` 63 | transition: transform 0.2s; 64 | &:hover { 65 | transform: scale(1.5); 66 | } 67 | `; 68 | 69 | const Header = ({ title, avatar, followingCount, followersCount }) => 70 | 71 | 72 | 73 | {[...title].map((letter, i) => 74 | 75 | {letter} 76 | 77 | )} 78 | 79 | 80 | 81 | 82 | 87 | 88 | 92 | 93 | 94 | 95 | 96 | 101 | 102 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | Followers 117 | 118 | {followersCount} 119 | 120 | 121 | 122 | Following 123 | 124 | {followingCount} 125 | 126 | 127 | 128 | 129 | ; 130 | 131 | export default Header; 132 | --------------------------------------------------------------------------------