diff --git a/solutions/src/error-boundaries/api/repos.js b/solutions/src/error-boundaries/api/repos.js new file mode 100644 index 0000000..1a3080a --- /dev/null +++ b/solutions/src/error-boundaries/api/repos.js @@ -0,0 +1,23 @@ +export const getRepos = username => { + return fetch(`https://api.github.com/users/${username}/repos?sort=updated`) + .then(response => response.json()) + .then(data => { + let repos = data.map(repo => { + return { + id: repo.id, + name: repo.name, + stars: repo.stargazers_count, + description: repo.description, + url: repo.html_url + }; + }); + repos = repos.sort((a, b) => b.stars - a.stars).slice(0, 5); + return repos; + }) + .catch(error => { + /* Error handling */ + return { + error + }; + }); +}; diff --git a/solutions/src/error-boundaries/api/user.js b/solutions/src/error-boundaries/api/user.js new file mode 100644 index 0000000..7eb946a --- /dev/null +++ b/solutions/src/error-boundaries/api/user.js @@ -0,0 +1,16 @@ +export const getUser = username => { + return fetch(`https://api.github.com/users/${username}`) + .then(response => response.json()) + .then(data => { + return { + username: data.login, + name: data.name, + photo: data.avatar_url, + bio: data.bio || "no description", + url: data.html_url + }; + }) + .catch(error => { + return { error }; + }); +}; diff --git a/solutions/src/error-boundaries/components/common/errorImage.js b/solutions/src/error-boundaries/components/common/errorImage.js new file mode 100644 index 0000000..432ae59 --- /dev/null +++ b/solutions/src/error-boundaries/components/common/errorImage.js @@ -0,0 +1,6 @@ +import React from "react"; +import errorImage from "../../img/error.jpg"; + +const ErrorImage = () => error; + +export default ErrorImage; diff --git a/solutions/src/error-boundaries/components/common/link.js b/solutions/src/error-boundaries/components/common/link.js new file mode 100644 index 0000000..3ce633d --- /dev/null +++ b/solutions/src/error-boundaries/components/common/link.js @@ -0,0 +1,21 @@ +import React from "react"; +import styled from "styled-components"; + +import A from "../library/anchor"; +import Blink from "../library/blink"; + +const Loading = styled(A)` + background: #6ed396; + width: 40%; + height: 22px; + margin-top: 10px; + &::before { + content: "x"; + } + animation: ${Blink} 2s linear infinite; +`; + +export default props => { + if (props.url) return {props.children}; + else return ; +}; diff --git a/solutions/src/error-boundaries/components/common/logo.js b/solutions/src/error-boundaries/components/common/logo.js new file mode 100644 index 0000000..16b3796 --- /dev/null +++ b/solutions/src/error-boundaries/components/common/logo.js @@ -0,0 +1,5 @@ +import React from "react"; +import githubLogo from "../../img/github-logo.png"; +const Logo = () => github; + +export default Logo; diff --git a/solutions/src/error-boundaries/components/common/nav.js b/solutions/src/error-boundaries/components/common/nav.js new file mode 100644 index 0000000..98a943a --- /dev/null +++ b/solutions/src/error-boundaries/components/common/nav.js @@ -0,0 +1,41 @@ +import React from "react"; +import styled, { injectGlobal } from "styled-components"; +import Logo from "./logo"; +import Helmet from "react-helmet"; + +injectGlobal` + body { + margin: 0; + padding: 0; + background: #EEE; + font-family: 'Nunito', sans-serif; + } +`; + +const NavBar = styled.div` + height: 30px; + padding: 10px; + background: #fff; + border-bottom: 1px solid #ddd; + text-align: center; + > img { + height: 30px; + } +`; + +const Nav = () => ( + + + + +); + +export default Nav; diff --git a/solutions/src/error-boundaries/components/library/anchor.js b/solutions/src/error-boundaries/components/library/anchor.js new file mode 100644 index 0000000..7a60833 --- /dev/null +++ b/solutions/src/error-boundaries/components/library/anchor.js @@ -0,0 +1,10 @@ +import styled from "styled-components"; + +const A = styled.a` + text-decoration: none; + color: #6ed396; + display: inline-block; + width: "20px"; +`; + +export default A; diff --git a/solutions/src/error-boundaries/components/library/avatar.js b/solutions/src/error-boundaries/components/library/avatar.js new file mode 100644 index 0000000..f563d31 --- /dev/null +++ b/solutions/src/error-boundaries/components/library/avatar.js @@ -0,0 +1,15 @@ +import React from 'react' +import styled from 'styled-components' + +const Avatar = styled.span` + display: inline-block; + height: 100px; + width: 100px; + border-radius: 50%; + border: 5px solid #EEE; + background-color: #EEE; + ${props => (props.src ? `background-image: url(${props.src})` : '')}; + background-size: cover; +` + +export default props => diff --git a/solutions/src/error-boundaries/components/library/blink.js b/solutions/src/error-boundaries/components/library/blink.js new file mode 100644 index 0000000..9a8f46d --- /dev/null +++ b/solutions/src/error-boundaries/components/library/blink.js @@ -0,0 +1,9 @@ +import { keyframes } from 'styled-components' + +const Blink = keyframes` + 0% {opacity: 0.1} + 50% {opacity: 0.3} + 100% {opacity: 0.1} +` + +export default Blink diff --git a/solutions/src/error-boundaries/components/library/button.js b/solutions/src/error-boundaries/components/library/button.js new file mode 100644 index 0000000..daaa889 --- /dev/null +++ b/solutions/src/error-boundaries/components/library/button.js @@ -0,0 +1,18 @@ +import styled from "styled-components"; + +const Button = styled.button` + background: #0eb550; + color: #fff; + border: 1px solid #1d99bd; + border-radius: 2px; + padding: 10px; + margin-top: 20px; + width: 100%; + outline: none; + font-size: 16px; + &:active { + background: #6ed396; + } +`; + +export default Button; diff --git a/solutions/src/error-boundaries/components/library/card.js b/solutions/src/error-boundaries/components/library/card.js new file mode 100644 index 0000000..3070808 --- /dev/null +++ b/solutions/src/error-boundaries/components/library/card.js @@ -0,0 +1,10 @@ +import styled from 'styled-components' + +const Card = styled.div` + background: #FFF; + border: 1px solid #DDD; + border-radius: 2px; + padding: 10px; +` + +export default Card diff --git a/solutions/src/error-boundaries/components/library/clear.js b/solutions/src/error-boundaries/components/library/clear.js new file mode 100644 index 0000000..67b0b93 --- /dev/null +++ b/solutions/src/error-boundaries/components/library/clear.js @@ -0,0 +1,7 @@ +import styled from 'styled-components' + +const Clear = styled.div` + clear: both; +` + +export default Clear diff --git a/solutions/src/error-boundaries/components/library/input.js b/solutions/src/error-boundaries/components/library/input.js new file mode 100644 index 0000000..00ff788 --- /dev/null +++ b/solutions/src/error-boundaries/components/library/input.js @@ -0,0 +1,18 @@ +import styled from "styled-components"; + +const Input = styled.input` + background: #fff; + border: 1px solid #ddd; + border-radius: 2px; + padding: 10px; + width: calc(100% - 20px); + outline: none; + font-size: 16px; + + &:hover, + &:focus { + border-color: #6ed396; + } +`; + +export default Input; diff --git a/solutions/src/error-boundaries/components/library/label.js b/solutions/src/error-boundaries/components/library/label.js new file mode 100644 index 0000000..a59b655 --- /dev/null +++ b/solutions/src/error-boundaries/components/library/label.js @@ -0,0 +1,10 @@ +import styled from "styled-components"; + +const Label = styled.div` + height: 30px; + padding: 10px; + text-align: center; + color: ${props => props.color || "#0EB550"}; +`; + +export default Label; diff --git a/solutions/src/error-boundaries/components/library/star.js b/solutions/src/error-boundaries/components/library/star.js new file mode 100644 index 0000000..015aeac --- /dev/null +++ b/solutions/src/error-boundaries/components/library/star.js @@ -0,0 +1,11 @@ +import styled from 'styled-components' + +const Star = styled.span` + margin: 10px; + &::after { + content: ' \\2605'; + color: gold; + } +` + +export default Star diff --git a/solutions/src/error-boundaries/img/error.jpg b/solutions/src/error-boundaries/img/error.jpg new file mode 100644 index 0000000..7f45d01 Binary files /dev/null and b/solutions/src/error-boundaries/img/error.jpg differ diff --git a/solutions/src/error-boundaries/img/github-logo.png b/solutions/src/error-boundaries/img/github-logo.png new file mode 100644 index 0000000..8f109fd Binary files /dev/null and b/solutions/src/error-boundaries/img/github-logo.png differ diff --git a/solutions/src/error-boundaries/index.js b/solutions/src/error-boundaries/index.js new file mode 100644 index 0000000..53932a3 --- /dev/null +++ b/solutions/src/error-boundaries/index.js @@ -0,0 +1,15 @@ +import React, { Component } from "react"; +import Profile from "./modules/Profile"; +import ErrorBoundary from "./modules/ErrorBoundary"; + +class App extends Component { + render() { + return ( + + + + ); + } +} + +export default App; diff --git a/solutions/src/error-boundaries/modules/ErrorBoundary/index.js b/solutions/src/error-boundaries/modules/ErrorBoundary/index.js new file mode 100644 index 0000000..b370586 --- /dev/null +++ b/solutions/src/error-boundaries/modules/ErrorBoundary/index.js @@ -0,0 +1,47 @@ +import React, { Component } from "react"; +import styled from "styled-components"; +import ErrorImage from "../../components/common/errorImage"; +import Card from "../../components/library/card"; +import Button from "../../components/library/button"; +import Label from "../../components/library/label"; +const ErrorCard = styled(Card)` + text-align: center; + box-sizing: border-box; + margin: 20px auto 0; + padding: 30px 50px; + width: 500px; + min-height: 100px; + color: #000000; + + @media (max-width: 600px) { + width: 90%; + } +`; + +export default class ErrorBoundary extends Component { + state = { hasError: false }; + + componentDidCatch(error, info) { + // Display fallback UI + this.setState({ hasError: true }); + // You can also log the error to an error reporting service + // logErrorToMyService(error, info); + } + reloadPage = () => { + window.location.reload(); + }; + render() { + console.log("chekc here", this.state); + if (this.state.hasError) { + // You can render any custom fallback UI + return ( + + + + + + ); + } + return this.props.children; + } +} diff --git a/solutions/src/error-boundaries/modules/Profile/description.js b/solutions/src/error-boundaries/modules/Profile/description.js new file mode 100644 index 0000000..f3efc3f --- /dev/null +++ b/solutions/src/error-boundaries/modules/Profile/description.js @@ -0,0 +1,8 @@ +import React from 'react' +import styled from 'styled-components' +const Description = styled.div` + color: #999; + font-size: 12px; +` + +export default props => {props.content} diff --git a/solutions/src/error-boundaries/modules/Profile/index.js b/solutions/src/error-boundaries/modules/Profile/index.js new file mode 100644 index 0000000..2c0e7e9 --- /dev/null +++ b/solutions/src/error-boundaries/modules/Profile/index.js @@ -0,0 +1,52 @@ +import React, { Component, Fragment } from "react"; +import { getRepos } from "../../api/repos"; +import { getUser } from "../../api/user"; +import ProfileInput from "./profile-input"; +import UserProfile from "./profile"; +import Repositories from "./repositories"; +import Card from "../../components/library/card"; +import Nav from "../../components/common/nav"; +import styled from "styled-components"; + +const RepositoriesCard = styled(Card)` + text-align: center; + box-sizing: border-box; + margin: 20px auto 0; + padding: 30px 50px; + width: 500px; + min-height: 100px; + color: #000000; + + @media (max-width: 600px) { + width: 90%; + } +`; +export default class Profile extends Component { + state = { + userRepos: [{}, {}, {}, {}, {}], + userProfile: {} + }; + getUserInfo = userName => { + getUser(userName).then(userProfile => this.setState({ userProfile })); + getRepos(userName).then(userRepos => this.setState({ userRepos })); + }; + render() { + const { userRepos, userProfile } = this.state; + let RepositoriesData = ( + + + + + ); + + if (!userProfile.url) { + RepositoriesData = No Data Found..; + } + return ( + + + {RepositoriesData} + + ); + } +} diff --git a/solutions/src/error-boundaries/modules/Profile/profile-input.js b/solutions/src/error-boundaries/modules/Profile/profile-input.js new file mode 100644 index 0000000..f8b5926 --- /dev/null +++ b/solutions/src/error-boundaries/modules/Profile/profile-input.js @@ -0,0 +1,67 @@ +import React from "react"; +import styled from "styled-components"; +import Card from "../../components/library/card"; +import Input from "../../components/library/input"; +import Button from "../../components/library/button"; +import Logo from "../../components/common/logo"; + +const ProfileInput = styled(Card)` + text-align: center; + box-sizing: border-box; + width: 500px; + margin: 20px auto 0; + padding: 30px 50px; + min-height: 100px; + + @media (max-width: 600px) { + width: 90%; + } + + > input { + margin-top: 30px; + } + + > a { + width: 100%; + margin-top: 10px; + } +`; + +export default class extends React.Component { + /* Set initial state */ + state = { username: "" }; + /* Change state on change */ + onChange = event => { + this.setState({ username: event.target.value }); + }; + /* Trigger submit on enter key */ + onKeyUp = event => { + if (event.which === 13) this.setState({ username: event.target.value }); + }; + /* Pick value from input on focus */ + onFocus = event => { + this.setState({ username: event.target.value }); + }; + getUserInfo = () => { + this.props.getUserInfo(this.state.username); + }; + /* + Compose presentation components inside our + ProfileInput components + */ + render() { + return ( + + + + + + ); + } +} diff --git a/solutions/src/error-boundaries/modules/Profile/profile.js b/solutions/src/error-boundaries/modules/Profile/profile.js new file mode 100644 index 0000000..ebc76d7 --- /dev/null +++ b/solutions/src/error-boundaries/modules/Profile/profile.js @@ -0,0 +1,30 @@ +import React from "react"; +import styled from "styled-components"; +import Card from "../../components/library/card"; +import Link from "../../components/common/link"; + +import Avatar from "../../components/library/avatar"; +import Description from "./description"; + +const ProfileCard = styled(Card)` + text-align: center; + box-sizing: border-box; + margin: 50px auto 0; + min-height: 180px; + + @media (max-width: 600px) { + width: 90%; + } +`; +const Profile = ({ userProfile }) => { + return ( + + +
+ {userProfile.name} +
+ +
+ ); +}; +export default Profile; diff --git a/solutions/src/error-boundaries/modules/Profile/repositories.js b/solutions/src/error-boundaries/modules/Profile/repositories.js new file mode 100644 index 0000000..35680d5 --- /dev/null +++ b/solutions/src/error-boundaries/modules/Profile/repositories.js @@ -0,0 +1,40 @@ +import React from "react"; +import styled from "styled-components"; +import Card from "../../components/library/card"; +import Star from "../../components/library/star"; +import Clear from "../../components/library/clear"; +import Link from "../../components/common/link"; + +const RepoList = styled(Card)` + margin: 10px auto; + box-sizing: border-box; + min-height: 215px; + + @media (max-width: 600px) { + width: 90%; + } +`; + +const Repo = styled.div` + color: #777; + > a { + float: left; + margin-top: 10px; + } + > span { + float: right; + } +`; + +export default props => ( + + {props.repos && + props.repos.map((repo, index) => ( + + {repo.name} + {repo.stars} + + + ))} + +);