├── .babelrc ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── src ├── components │ ├── AllUsers.js │ ├── AllUsersContainer.js │ ├── CurrentUser.js │ ├── CurrentUserContainer.js │ ├── SingleUser.js │ ├── User.js │ └── UserContainer.js ├── config.js ├── index.html └── main.js ├── webpack.local.config.js └── webpack.production.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-1" 6 | ], 7 | "plugins": [ 8 | "transform-runtime", 9 | "syntax-async-functions" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "ecmaVersion": 7, 4 | "parser": "babel-eslint", 5 | "env": { 6 | "browser": true, 7 | "node": true, 8 | "es6": true 9 | }, 10 | "globals": { 11 | "__DEV__": true 12 | }, 13 | "rules": { 14 | "react/jsx-uses-react": 2, 15 | "react/jsx-no-undef": 2, 16 | "react/wrap-multilines": 2, 17 | "max-len": [2, 80], 18 | "no-console": 0, 19 | "no-else-return": 0, 20 | "react/jsx-no-bind": 0, 21 | "react/jsx-closing-bracket-location": [2, "after-props"], 22 | "no-use-before-define": 0, 23 | "new-cap": 0 24 | }, 25 | "plugins": [ 26 | "react" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build 2 | build 3 | build 4 | 5 | # Logs 6 | logs 7 | *.log 8 | 9 | # Compiled binary addons (http://nodejs.org/api/addons.html) 10 | build/Release 11 | 12 | # Dependency directory 13 | node_modules 14 | 15 | # Users Environment Variables 16 | .lock-wscript 17 | 18 | .idea 19 | 20 | # Relay 21 | 22 | scripts/RelaySchema.json 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present, Reindex Software 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 | # reindex-starter-kit-react-apollo 2 | 3 | Comprehensive Reindex starter kit with React, Apollo Client and Auth0 for 4 | Authentication. It also includes react-router, so it's easy to add new 5 | routes. 6 | 7 | ## Running 8 | 9 | 1. Install Reindex CLI and login 10 | 11 | ```sh 12 | npm install -g reindex-cli 13 | reindex login 14 | ``` 15 | 16 | 2. Set up Auth0 in Reindex 17 | 18 | Open GraphiQL console 19 | 20 | ```sh 21 | reindex graphiql 22 | ``` 23 | 24 | In GraphiQL, create ReindexAuthenticationProvider for Auth0. 25 | 26 | ```graphql 27 | mutation { 28 | createReindexAuthenticationProvider(input: { 29 | type: auth0, 30 | isEnabled: true, 31 | domain: "YOUR-AUTH0-DOMAIN.auth0.com", 32 | clientId: "YOUR-AUTH0-CLIENT-ID", 33 | clientSecret: "YOUR-AUTH0-CLIENT-SECRET", 34 | }) { 35 | id 36 | } 37 | } 38 | ``` 39 | 40 | 3. Install dependencies 41 | 42 | ``` 43 | npm install 44 | ``` 45 | 46 | 4. Edit `src/config.js` to include your Reindex and Auth0 credentials 47 | 48 | ```js 49 | export default { 50 | REINDEX_URL: 'https://YOUR-REINDEX-URL.myreindex.com', 51 | AUTH0_DOMAIN: 'YOUR-AUTH0-DOMAIN.auth0.com', 52 | AUTH0_CLIENT_ID: 'YOUR-AUTH0-CLIENT-ID', 53 | }; 54 | ``` 55 | 56 | 4. Run development server 57 | 58 | ``` 59 | npm start 60 | ``` 61 | 62 | Go to `http://localhost:3000` 63 | 64 | ## Deploying 65 | 66 | 1. Build production version 67 | 68 | ``` 69 | npm run build 70 | ``` 71 | 72 | 2. You can now deploy `build/` folder to static hosting of your choice. We 73 | recommend [surge.sh](https://surge.sh). 74 | 75 | ``` 76 | npm install -g surge 77 | surge build/ 78 | ``` 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reindex-starter-kit-apollo", 3 | "version": "1.0.0", 4 | "description": "starter kit for React and Apollo Client with Reindex", 5 | "main": "src/main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "webpack-dev-server --config webpack.local.config.js", 9 | "build": "webpack --progress --config webpack.production.config.js", 10 | "clean": "rimraf build" 11 | }, 12 | "author": "Reindex (https://www.reindex.io)", 13 | "license": "MIT", 14 | "dependencies": { 15 | "apollo-client": "^0.3.20", 16 | "auth0-lock": "^9.0.2", 17 | "babel-polyfill": "^6.7.4", 18 | "bluebird": "^3.3.4", 19 | "graphql-tag": "^0.1.7", 20 | "json-loader": "^0.5.4", 21 | "react": "^15.0.1", 22 | "react-apollo": "^0.3.10", 23 | "react-dom": "^15.0.1", 24 | "react-router": "^2.0.1", 25 | "reindex-js": "^0.4.0", 26 | "transform-loader": "^0.2.3" 27 | }, 28 | "devDependencies": { 29 | "autoprefixer": "^6.3.6", 30 | "babel-eslint": "^6.0.2", 31 | "babel-loader": "^6.2.4", 32 | "babel-plugin-transform-runtime": "^6.7.5", 33 | "babel-preset-es2015": "^6.6.0", 34 | "babel-preset-react": "^6.5.0", 35 | "babel-preset-stage-1": "^6.5.0", 36 | "babel-register": "^6.7.2", 37 | "css-loader": "^0.23.1", 38 | "eslint": "^2.7.0", 39 | "eslint-config-airbnb": "^9.0.1", 40 | "eslint-plugin-import": "^1.8.0", 41 | "eslint-plugin-jsx-a11y": "^1.2.2", 42 | "eslint-plugin-react": "^5.1.1", 43 | "express": "^4.13.4", 44 | "extract-text-webpack-plugin": "^1.0.1", 45 | "file-loader": "^0.8.5", 46 | "html-webpack-plugin": "^2.17.0", 47 | "postcss-loader": "^0.9.1", 48 | "postcss-nested": "^1.0.0", 49 | "react-hot-loader": "^1.3.0", 50 | "rimraf": "^2.5.2", 51 | "style-loader": "^0.13.1", 52 | "webpack": "^1.12.15", 53 | "webpack-dev-middleware": "^1.6.1", 54 | "webpack-dev-server": "^1.14.1", 55 | "webpack-hot-middleware": "^2.10.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/AllUsers.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import User from './User'; 4 | 5 | const AllUsers = (props) => { 6 | if (props.viewer.loading) { 7 | return ( 8 |
Loading...
9 | ); 10 | } else { 11 | return ( 12 |
13 |

All Users

14 | 21 |
22 | ); 23 | } 24 | }; 25 | 26 | export default AllUsers; 27 | -------------------------------------------------------------------------------- /src/components/AllUsersContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-apollo'; 2 | import gql from 'graphql-tag'; 3 | 4 | import AllUsers from '../components/AllUsers'; 5 | 6 | function mapQueriesToProps() { 7 | return { 8 | viewer: { 9 | query: gql` 10 | query { 11 | viewer { 12 | allUsers(first: 1000) { 13 | edges { 14 | node { 15 | id 16 | } 17 | } 18 | } 19 | } 20 | } 21 | `, 22 | }, 23 | }; 24 | } 25 | 26 | const AllUsersContainer = connect({ 27 | mapQueriesToProps, 28 | })(AllUsers); 29 | 30 | export default AllUsersContainer; 31 | -------------------------------------------------------------------------------- /src/components/CurrentUser.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import User from './User'; 4 | 5 | const CurrentUser = (props) => { 6 | if (props.viewer.loading) { 7 | return ( 8 |
Loading...
9 | ); 10 | } else { 11 | return ( 12 |
13 |

Current User

14 | 15 |
16 | ); 17 | } 18 | } 19 | 20 | export default CurrentUser; 21 | -------------------------------------------------------------------------------- /src/components/CurrentUserContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-apollo'; 2 | import gql from 'graphql-tag'; 3 | 4 | import CurrentUser from '../components/CurrentUser'; 5 | 6 | function mapQueriesToProps() { 7 | return { 8 | viewer: { 9 | query: gql` 10 | query { 11 | viewer { 12 | user { 13 | id 14 | } 15 | } 16 | } 17 | `, 18 | }, 19 | }; 20 | } 21 | 22 | const CurrentUserContainer = connect({ 23 | mapQueriesToProps, 24 | })(CurrentUser); 25 | 26 | export default CurrentUserContainer; 27 | -------------------------------------------------------------------------------- /src/components/SingleUser.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import User from './User'; 4 | 5 | const SingleUser = (props) => { 6 | if (props.user.loading) { 7 | return ( 8 |
Loading...
9 | ); 10 | } else { 11 | return ( 12 |
13 |

User by id

14 | 15 |
16 | ); 17 | } 18 | } 19 | 20 | export default SingleUser; 21 | -------------------------------------------------------------------------------- /src/components/User.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | const User = (props) => ( 5 |
6 | User with id {props.user.id} 7 | {' - '} 8 | Go to 9 |
10 | ); 11 | 12 | export default User; 13 | -------------------------------------------------------------------------------- /src/components/UserContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-apollo'; 2 | import gql from 'graphql-tag'; 3 | 4 | import SingleUser from './SingleUser'; 5 | 6 | function mapQueriesToProps({ ownProps }) { 7 | return { 8 | user: { 9 | query: gql` 10 | query($id: ID!) { 11 | userById(id: $id) { 12 | id 13 | } 14 | } 15 | `, 16 | variables: { 17 | id: ownProps.params.userId, 18 | }, 19 | }, 20 | }; 21 | } 22 | 23 | const UserContainer = connect({ 24 | mapQueriesToProps, 25 | })(SingleUser); 26 | 27 | export default UserContainer; 28 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | REINDEX_URL: '', 3 | AUTH0_DOMAIN: '', 4 | AUTH0_CLIENT_ID: '', 5 | }; 6 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Reindex React Starter Kit 5 | 6 | 7 | 8 |
9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { fromCallback } from 'bluebird'; 2 | import React, { Component } from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import { Router, Route, Link, IndexRoute, browserHistory } from 'react-router'; 5 | import Reindex from 'reindex-js'; 6 | import Auth0Lock from 'auth0-lock'; 7 | 8 | import ApolloClient, { createNetworkInterface } from 'apollo-client'; 9 | import { ApolloProvider } from 'react-apollo'; 10 | 11 | import Config from './config'; 12 | 13 | import CurrentUserContainer from './components/CurrentUserContainer'; 14 | import AllUsersContainer from './components/AllUsersContainer'; 15 | import UserContainer from './components/UserContainer'; 16 | 17 | const reindex = new Reindex(Config.REINDEX_URL); 18 | const networkInterface = createNetworkInterface( 19 | `${Config.REINDEX_URL}/graphql` 20 | ); 21 | networkInterface.use([{ 22 | applyMiddleware(req, next) { 23 | if (!req.options.headers) { 24 | req.options.headers = {}; 25 | } 26 | req.options.headers = { 27 | ...req.options.headers, 28 | ...reindex.getAuthenticationHeaders(), 29 | } 30 | next(); 31 | } 32 | }]); 33 | const client = new ApolloClient({ 34 | networkInterface, 35 | }); 36 | 37 | class Main extends Component { 38 | constructor() { 39 | super(); 40 | this.state = { 41 | isLoggedIn: reindex.isLoggedIn(), 42 | }; 43 | } 44 | 45 | componentDidMount() { 46 | reindex.addListener('tokenChange', this.handleTokenChange); 47 | } 48 | 49 | componentWillUnmount() { 50 | reindex.removeListener('tokenChange', this.handleTokenChange); 51 | } 52 | 53 | handleTokenChange = () => { 54 | this.setState({ isLoggedIn: reindex.isLoggedIn() }); 55 | }; 56 | 57 | makeContent() { 58 | if (this.state.isLoggedIn) { 59 | return ( 60 |
61 |

Routes

62 |
63 | Logged-in user 64 |
65 |
66 | All users 67 |
68 | {this.props.children} 69 |
70 | ); 71 | } else { 72 | return ( 73 |
74 | You are not logged-in 75 |
76 | ); 77 | } 78 | } 79 | 80 | render() { 81 | return ( 82 |
83 |
84 | {this.state.isLoggedIn ? 85 | Logout : 86 | Login} 87 |
88 | {this.makeContent()} 89 |
90 | ); 91 | } 92 | } 93 | 94 | async function handleLogin(nextState, replace, callback) { 95 | if (!reindex.isLoggedIn()) { 96 | try { 97 | const lock = new Auth0Lock(Config.AUTH0_CLIENT_ID, Config.AUTH0_DOMAIN); 98 | const auth0Result = await fromCallback( 99 | (callback) => lock.show({}, callback), 100 | { multiArgs: true }, 101 | ); 102 | 103 | console.log(auth0Result); 104 | 105 | // Login to Reindex 106 | const reindexResponse = await reindex.loginWithToken( 107 | 'auth0', 108 | auth0Result[1], 109 | ); 110 | for (const error of reindexResponse.errors || []) { 111 | console.log(error); 112 | } 113 | } catch (e) { 114 | console.log(e); 115 | } 116 | } 117 | 118 | replace('/'); 119 | callback(); 120 | } 121 | 122 | function handleLogout() { 123 | reindex.logout(); 124 | // Clear Store 125 | document.location.href = '/'; 126 | } 127 | 128 | ReactDOM.render( 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | , 140 | document.getElementById('app') 141 | ); 142 | -------------------------------------------------------------------------------- /webpack.local.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | /** 6 | * This is the Webpack configuration file for local development. It contains 7 | * local-specific configuration such as the React Hot Loader, as well as: 8 | * 9 | * - The entry point of the application 10 | * - Where the output file should be 11 | * - Which loaders to use on what files to properly transpile the source 12 | * 13 | * For more information, see: http://webpack.github.io/docs/configuration.html 14 | */ 15 | module.exports = { 16 | // Efficiently evaluate modules with source maps 17 | devtool: 'eval', 18 | 19 | // Set entry point to ./src/main and include necessary files for hot load 20 | entry: ['babel-polyfill', './src/main'], 21 | 22 | // This will not actually create a bundle.js file in ./build. It is used 23 | // by the dev server for dynamic hot loading. 24 | output: { 25 | path: __dirname + '/build/', 26 | filename: 'app.js', 27 | publicPath: '/' 28 | }, 29 | 30 | // Necessary plugins for hot load 31 | plugins: [ 32 | new webpack.HotModuleReplacementPlugin(), 33 | new webpack.NoErrorsPlugin(), 34 | new HtmlWebpackPlugin({ 35 | template: 'src/index.html', 36 | inject: 'body', 37 | }), 38 | ], 39 | 40 | // Transform source code using Babel and React Hot Loader 41 | module: { 42 | loaders: [ 43 | { 44 | test: /\.js$/, 45 | exclude: /node_modules/, 46 | loaders: ['react-hot', 'babel-loader'], 47 | }, 48 | { 49 | test: /\.css$/, 50 | loaders: [ 51 | 'style-loader', 52 | 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader', 53 | ], 54 | }, 55 | 56 | // auth0 lock 57 | { 58 | test: /node_modules[\\\/]auth0-lock[\\\/].*\.js$/, 59 | loaders: [ 60 | 'transform-loader/cacheable?brfs', 61 | 'transform-loader/cacheable?packageify', 62 | ], 63 | }, 64 | { 65 | test: /node_modules[\\\/]auth0-lock[\\\/].*\.ejs$/, 66 | loader: 'transform-loader/cacheable?ejsify', 67 | }, 68 | { 69 | test: /\.json$/, 70 | loader: 'json-loader', 71 | }, 72 | ], 73 | }, 74 | 75 | // Automatically transform files with these extensions 76 | resolve: { 77 | extensions: ['', '.js', '.jsx', '.css'], 78 | }, 79 | 80 | // Additional plugins for CSS post processing using postcss-loader 81 | postcss: [ 82 | require('autoprefixer'), // Automatically include vendor prefixes 83 | require('postcss-nested'), // Enable nested rules, like in Sass 84 | ], 85 | 86 | devServer: { 87 | host: '127.0.0.1', 88 | port: 3000, 89 | historyApiFallback: true, 90 | hot: true, 91 | inline: true, 92 | }, 93 | }; 94 | -------------------------------------------------------------------------------- /webpack.production.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | /** 6 | * This is the Webpack configuration file for production. 7 | */ 8 | module.exports = { 9 | devtool: 'source-map', 10 | 11 | entry: ['babel-polyfill', './src/main'], 12 | 13 | output: { 14 | path: __dirname + '/build/', 15 | filename: 'app.js?[hash]', 16 | }, 17 | 18 | plugins: [ 19 | new ExtractTextPlugin('app.css?[hash]', { allChunks: true }), 20 | new HtmlWebpackPlugin({ 21 | template: 'src/index.html', 22 | inject: 'body', 23 | }), 24 | new webpack.optimize.OccurrenceOrderPlugin(), 25 | new webpack.DefinePlugin({ 26 | 'process.env': { 27 | 'NODE_ENV': JSON.stringify('production'), 28 | }, 29 | }), 30 | new webpack.optimize.UglifyJsPlugin({ 31 | compressor: { 32 | warnings: false 33 | }, 34 | }), 35 | ], 36 | 37 | module: { 38 | loaders: [ 39 | { 40 | test: /\.js$/, 41 | exclude: /node_modules/, 42 | loader: 'babel-loader', 43 | }, 44 | { 45 | test: /\.css$/, 46 | loader: ExtractTextPlugin.extract( 47 | 'style-loader', 48 | 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader' 49 | ), 50 | }, 51 | 52 | // auth0 lock 53 | // auth0 lock 54 | { 55 | test: /node_modules[\\\/]auth0-lock[\\\/].*\.js$/, 56 | loaders: [ 57 | 'transform-loader/cacheable?brfs', 58 | 'transform-loader/cacheable?packageify', 59 | ], 60 | }, 61 | { 62 | test: /node_modules[\\\/]auth0-lock[\\\/].*\.ejs$/, 63 | loader: 'transform-loader/cacheable?ejsify', 64 | }, 65 | { 66 | test: /\.json$/, 67 | loader: 'json-loader', 68 | }, 69 | ], 70 | }, 71 | 72 | resolve: { 73 | extensions: ['', '.js', '.jsx', '.css'], 74 | }, 75 | 76 | postcss: [ 77 | require('autoprefixer'), 78 | require('postcss-nested'), 79 | ], 80 | }; 81 | --------------------------------------------------------------------------------