├── .gitignore ├── LICENSE.MD ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── renovate.json ├── src ├── App.css ├── App.js ├── App.test.js ├── common │ ├── RouteUtil.jsx │ └── utils.js ├── components │ ├── Header.js │ ├── Tweet.js │ └── TweetItem.js ├── constant.js ├── graphql │ ├── mutation.js │ ├── query.js │ ├── resolver.js │ ├── subscription.js │ └── typeDefs.js ├── images │ └── twister.png ├── index.css ├── index.js ├── logo.svg ├── registerServiceWorker.js ├── router │ └── router.js ├── screens │ ├── HomePage.jsx │ ├── LandingPage.jsx │ ├── LoginPage.jsx │ ├── LogoutPage.jsx │ ├── SignupPage.jsx │ └── style.js └── styles │ ├── main.css │ ├── normalize.css │ └── skeleton.css └── yarn.lock /.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 | -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Awesome GraphQL Space 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

React GraphQL Boilerplate

2 | 3 |
4 | 5 | ![](https://imgur.com/ousyQaC.png) 6 | 7 |
Bootstrap your fullstack GraphQL app within seconds
8 | 9 |
10 | 11 | ## Features 12 | 13 | - **Easily extensible**: A boilerplate only provides the minimum setup so you can tailor the API to your use case. 14 | - **Best practices**: Each boilerplate incorporates best practices from the GraphQL community. 15 | 16 | 17 | ## Project setup 18 | ``` 19 | npm install 20 | ``` 21 | 22 | ### Compiles and hot-reloads for development 23 | ``` 24 | npm run start 25 | ``` 26 | 27 | ## License 28 | 29 | MIT 30 | 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app-manjula", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "apollo-boost": "^0.1.13", 7 | "apollo-cache-inmemory": "^1.2.6", 8 | "apollo-client": "^2.3.7", 9 | "apollo-link": "^1.2.2", 10 | "apollo-link-error": "^1.1.0", 11 | "apollo-link-http": "^1.5.4", 12 | "apollo-link-state": "^0.4.1", 13 | "apollo-link-ws": "^1.0.8", 14 | "graphql": "^0.13.2", 15 | "graphql-tag": "^2.9.2", 16 | "jwt-decode": "^2.2.0", 17 | "react": "^16.4.2", 18 | "react-apollo": "^2.1.9", 19 | "react-dom": "^16.4.2", 20 | "react-infinite-scroller": "^1.2.0", 21 | "react-onclickoutside": "^6.7.1", 22 | "react-router-dom": "^4.3.1", 23 | "react-scripts": "1.1.4", 24 | "styled-components": "^3.4.1", 25 | "subscriptions-transport-ws": "^0.9.14" 26 | }, 27 | "scripts": { 28 | "start": "react-scripts start", 29 | "build": "react-scripts build", 30 | "test": "react-scripts test --env=jsdom", 31 | "eject": "react-scripts eject" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-graphql-space/react-graphql/6041115e1e330c9d8b6f5115844c91d93145c890/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #f5f8fa 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App { 11 | text-align:center; 12 | } 13 | 14 | 15 | .App-header { 16 | background-color: #222; 17 | height: 80px; 18 | padding: 20px; 19 | color: white; 20 | } 21 | 22 | .App-title { 23 | font-size: 1.5em; 24 | } 25 | 26 | .App-intro { 27 | font-size: large; 28 | } 29 | 30 | @keyframes App-logo-spin { 31 | from { transform: rotate(0deg); } 32 | to { transform: rotate(360deg); } 33 | } 34 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react" 2 | import AppRouter from "./router/router" 3 | import { AUTH_TOKEN } from "./constant"; 4 | 5 | class App extends Component { 6 | state = { 7 | canAccess: false 8 | } 9 | 10 | componentDidMount(){ 11 | const app = localStorage.getItem(AUTH_TOKEN); 12 | 13 | if(app === null){ 14 | this.setState({canAccess: false}); 15 | }else if(app === ''){ 16 | this.setState({canAccess: false}); 17 | }else if(app.length > 0){ 18 | this.setState({canAccess: true}); 19 | } 20 | } 21 | render() { 22 | const { canAccess } = this.state; 23 | return ( 24 | 25 | ); 26 | } 27 | } 28 | 29 | export default App; -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/common/RouteUtil.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Route, Redirect } from "react-router"; 4 | 5 | /** 6 | * Component that protects route from unauthorized users. 7 | * @type {Object} 8 | */ 9 | export class AuthRoute extends Component { 10 | 11 | static propTypes = { 12 | component: PropTypes.func, 13 | path: PropTypes.string, 14 | name: PropTypes.string, 15 | exact: PropTypes.bool, 16 | strict: PropTypes.bool 17 | }; 18 | 19 | render() { 20 | let { canAccess, component, path, name, exact, strict } = this.props; 21 | let routeProps = { 22 | path, 23 | component, 24 | name, 25 | exact, 26 | strict 27 | }; 28 | 29 | if (canAccess) { 30 | return ; 31 | } else { 32 | return ; 33 | } 34 | } 35 | } 36 | 37 | export class UnauthRoute extends Component { 38 | constructor(props) { 39 | super(props); 40 | } 41 | 42 | static propTypes = { 43 | component: PropTypes.func, 44 | path: PropTypes.string, 45 | name: PropTypes.string, 46 | exact: PropTypes.bool, 47 | strict: PropTypes.bool 48 | }; 49 | 50 | render() { 51 | let { canAccess, component, path, name, exact, strict } = this.props; 52 | let routeProps = { 53 | path, 54 | component, 55 | name, 56 | exact, 57 | strict 58 | }; 59 | 60 | if (canAccess) { 61 | return ; 62 | } else { 63 | return ; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/common/utils.js: -------------------------------------------------------------------------------- 1 | import { AUTH_TOKEN } from "../constant"; 2 | 3 | export const AuthUtil = { 4 | 5 | getToken(){ 6 | localStorage.getItem(AUTH_TOKEN); 7 | }, 8 | 9 | setToken(token){ 10 | console.log(token); 11 | localStorage.setItem(AUTH_TOKEN, token); 12 | console.log('hello'); 13 | } 14 | 15 | }; -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import React from 'react'; 3 | import { 4 | NavLink, 5 | Link, 6 | BrowserRouter as Router, 7 | Route, 8 | Switch, 9 | Redirect, 10 | } from 'react-router-dom' 11 | import { GET_AUTH_STATUS } from '../graphql/query'; 12 | import { Query } from "react-apollo"; 13 | 14 | const HeaderStyle = styled.div` 15 | background-color: #009CFA; 16 | height: 50px; 17 | padding: 20px; 18 | color: white; 19 | font-size: 24px; 20 | font-family: 'Open Sans',-apple-system,'BlinkMacSystemFont','Arial',sans-serif; 21 | `; 22 | 23 | const Twister = styled.div` 24 | float:left; 25 | font-size:30px; 26 | ` 27 | 28 | const linkStyle ={ 29 | color: "white", 30 | padding: "15px", 31 | float: "right" 32 | } 33 | 34 | const Header = () => { 35 | return( 36 |
37 | 38 | {({ loading, error, data, fetchMore }) => { 39 | if (loading) { 40 | return
loading
; 41 | } 42 | 43 | if (error) { 44 | console.log(error) 45 | return
error
; 46 | } 47 | 48 | console.log(data.getAuthStatus.loggedIn); 49 | return( 50 | 51 | {data.getAuthStatus.loggedIn ? Logout : 52 | 53 | Login 54 | Signup 55 | Twister 56 | } 57 | 58 | ) 59 | }} 60 | 61 |
62 |
63 | ) 64 | } 65 | 66 | 67 | 68 | 69 | export default Header 70 | 71 | -------------------------------------------------------------------------------- /src/components/Tweet.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import onClickOutside from "react-onclickoutside"; 4 | import { Mutation } from 'react-apollo'; 5 | import { POST } from '../graphql/mutation'; 6 | import { AuthUtil } from '../common/utils'; 7 | import { AUTH_TOKEN } from '../constant'; 8 | import { 9 | NavLink 10 | } from 'react-router-dom' 11 | 12 | const Button = styled.button` 13 | display: inline-block; 14 | border: 1px solid #1da1f2; 15 | color: #fff; 16 | border-radius: 100px; 17 | box-shadow: none; 18 | cursor: pointer; 19 | font-size: 14px; 20 | font-weight: bold; 21 | line-height: 20px; 22 | padding: 6px 16px; 23 | position: relative; 24 | text-align: center; 25 | white-space: nowrap; 26 | background-color: #EC4972; 27 | border-color: transparent; 28 | margin: 12px; 29 | font-size: 14px; 30 | font-weight: 400; 31 | text-align: center; 32 | white-space: nowrap; 33 | vertical-align: middle; 34 | cursor: pointer; 35 | border: 1px solid transparent; 36 | ` 37 | const Section = styled.section` 38 | min-height: 20px; 39 | padding: 19px; 40 | margin-bottom: 20px; 41 | background-color: #f5f5f5; 42 | border: 1px solid #e3e3e3; 43 | border-radius: 4px; 44 | background-color: #E81C4F; 45 | background: rgba(232,28,79,0.1); 46 | `; 47 | 48 | export const Flex = styled.div` 49 | display: flex; 50 | flex: grow; 51 | justify-content: space-between; 52 | `; 53 | 54 | export const Row = styled.div` 55 | display: flex; 56 | justify-content: center; 57 | direction: row; 58 | padding:20px; 59 | `; 60 | 61 | const TextArea = styled.textarea` 62 | display: block; 63 | width: 100%; 64 | font-size: 14px; 65 | line-height: 1.42857143; 66 | color: #555; 67 | background-color: #fff; 68 | background-image: none; 69 | border: 1px solid #ccc; 70 | border-radius: 4px; 71 | border-color: #F5A4B8; 72 | box-shadow: 0 0 0 1px #F5A4B8; 73 | ` 74 | 75 | class Tweet extends React.Component { 76 | constructor(props) { 77 | super(props); 78 | this.state = { 79 | text: '', 80 | remainingChar: 140, 81 | addPhotoStatus: false, 82 | showButtons: false 83 | } 84 | } 85 | 86 | handleChange(e) { 87 | 88 | this.setState({ 89 | text: e.target.value, 90 | }) 91 | } 92 | 93 | addPhoto(e) { 94 | this.setState({ 95 | addPhotoStatus: !this.state.addPhotoStatus, 96 | }); 97 | } 98 | 99 | remainingChar () { 100 | if (this.state.addPhotoStatus) { 101 | return 140 - 23 - (this.state.text.length); 102 | } else { 103 | return 140 - (this.state.text.length); 104 | } 105 | } 106 | 107 | overflowAlert () { 108 | 109 | if (this.remainingChar() < 0) { 110 | let beforeOverflowText; 111 | let overflowText; 112 | 113 | if (this.state.addPhotoStatus) { 114 | beforeOverflowText = this.state.text.substring(140 - 10 - 23, 140 -23); 115 | overflowText = this.state.text.substring(140 - 23); 116 | } else { 117 | beforeOverflowText = this.state.text.substring(140 - 10, 140); 118 | overflowText = this.state.text.substring(140); 119 | } 120 | 121 | return ( 122 | 123 | // console.log('before: ' + beforeOverflowText); 124 |
125 | Oops! Too Long: 126 |  ...{beforeOverflowText} 127 | {overflowText} 128 |
129 | ); 130 | } 131 | } 132 | 133 | handleClickOutside(evt) { 134 | const { showButtons } = this.state; 135 | 136 | if(showButtons){ 137 | this.setState({showButtons: false}); 138 | } 139 | } 140 | 141 | render() { 142 | const { showButtons, text } = this.state; 143 | 144 | return ( 145 |
146 | 147 | {(mutate, {loading, error}) => ( 148 |
149 | {this.overflowAlert() } 150 |
151 |