├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── roc.setup.json └── template ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── LICENSE ├── package.json ├── public └── favicon.png ├── roc.config.js └── src ├── components ├── clicker │ ├── index.js │ └── style.scss ├── fetching │ ├── index.js │ └── style.scss ├── header │ ├── index.js │ ├── roc.png │ └── style.scss ├── index.js ├── redux │ ├── index.js │ └── style.scss ├── reset.scss ├── start │ ├── ext.png │ ├── index.js │ ├── index.spec.js │ └── style.scss └── style.scss ├── redux ├── clicker.js ├── reducers.js └── repo.js └── routes.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Verdens Gang AS 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 | # roc-template-web-app-react 2 | 3 | Template for creating a application that builds on [roc-package-web-app-react](https://github.com/rocjs/roc-package-web-app-react). 4 | 5 | ## Install 6 | 7 | `$ roc init rocjs/roc-template-web-app-react` 8 | 9 | _This repository is a default alternative in the Roc CLI and can be used directly using `$ roc init web-app-react`_. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-template-web-app-react", 3 | "version": "1.1.0-next.1", 4 | "description": "A simple start on a generic React web application for Roc", 5 | "author": "Verdens Gang AS", 6 | "license": "MIT", 7 | "keywords": [ 8 | "roc", 9 | "roc-template", 10 | "react", 11 | "universal", 12 | "isomorphic", 13 | "redux" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /roc.setup.json: -------------------------------------------------------------------------------- 1 | { 2 | "prompts": { 3 | "name": { 4 | "type": "string", 5 | "required": true, 6 | "message": "Project name" 7 | }, 8 | "description": { 9 | "type": "string", 10 | "required": false, 11 | "message": "Project description", 12 | "default": "A Roc project" 13 | }, 14 | "author": { 15 | "type": "string", 16 | "message": "Author" 17 | }, 18 | "license": { 19 | "type": "list", 20 | "choices": [{ 21 | "name": "MIT" 22 | }, { 23 | "name": "UNLICENSED" 24 | }] 25 | }, 26 | "port": { 27 | "type": "input", 28 | "message": "Port", 29 | "default": 3000 30 | }, 31 | "title": { 32 | "type": "string", 33 | "message": "" 34 | }, 35 | "fetchExample": { 36 | "type": "confirm", 37 | "message": "Include data-fetching example?" 38 | }, 39 | "reduxExample": { 40 | "type": "confirm", 41 | "message": "Include Redux example with data-fetching?" 42 | }, 43 | "testJest": { 44 | "type": "confirm", 45 | "message": "Include Jest for testing?" 46 | } 47 | }, 48 | "filters": { 49 | "LICENSE": "license === 'MIT'", 50 | "src/redux/**/*": "reduxExample", 51 | "src/components/redux/**/*": "reduxExample", 52 | "src/components/clicker/**/*": "reduxExample", 53 | "src/components/fetching/**/*": "fetchExample", 54 | "src/components/**/*.spec.js": "testJest" 55 | }, 56 | "completionMessage": "To get started:\n\n{{#unless inPlace}} cd {{destDirName}}\n{{/unless}} npm install\n npm run dev\n\n It will open your default browser when ready.\n You can change this by setting 'dev.browsersync.options.open' to 'false' in roc.config.js" 57 | } 58 | -------------------------------------------------------------------------------- /template/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs. 2 | # Requires EditorConfig JetBrains Plugin - http://github.com/editorconfig/editorconfig-jetbrains 3 | 4 | # Set this file as the topmost .editorconfig 5 | # (multiple files can be used, and are applied starting from current document location) 6 | root = true 7 | 8 | # Use bracketed regexp to target specific file types or file locations 9 | [*.{js,json}] 10 | 11 | # Use hard or soft tabs ["tab", "space"] 12 | indent_style = space 13 | 14 | # Size of a single indent [an integer, "tab"] 15 | indent_size = tab 16 | 17 | # Number of columns representing a tab character [an integer] 18 | tab_width = 2 19 | 20 | # Line breaks representation ["lf", "cr", "crlf"] 21 | end_of_line = lf 22 | 23 | # ["latin1", "utf-8", "utf-16be", "utf-16le"] 24 | charset = utf-8 25 | 26 | # Remove any whitespace characters preceding newline characters ["true", "false"] 27 | trim_trailing_whitespace = true 28 | 29 | # Ensure file ends with a newline when saving ["true", "false"] 30 | insert_final_newline = true 31 | -------------------------------------------------------------------------------- /template/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /template/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "airbnb", 4 | "roc" 5 | ], 6 | "parser": "babel-eslint", 7 | "rules": { 8 | "react/jsx-filename-extension": 0 9 | }{{#if testJest}}, 10 | "env": { 11 | "jest": true 12 | } 13 | {{/if}} 14 | } 15 | -------------------------------------------------------------------------------- /template/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build 3 | node_modules 4 | -------------------------------------------------------------------------------- /template/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 {{ author }} 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 | -------------------------------------------------------------------------------- /template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ name }}", 3 | "version": "1.0.0", 4 | "description": "{{ description }}", 5 | "author": "{{ author }}", 6 | "license": "{{ license }}", 7 | {{#if_eq license "UNLICENSED"}} 8 | "private": true, 9 | {{/if_eq}} 10 | "scripts": { 11 | "build": "roc build", 12 | "dev": "roc dev", 13 | "lint": "eslint .", 14 | "start": "roc start"{{#if testJest}}, 15 | "test": "roc test" 16 | {{/if}} 17 | }, 18 | "dependencies": { 19 | {{#if fetchExample}} 20 | "isomorphic-fetch": "^2.2.1", 21 | {{else if reduxExample}} 22 | "isomorphic-fetch": "^2.2.1", 23 | {{/if}} 24 | "roc-package-web-app-react": "^1.0.0-beta.16" 25 | }, 26 | "devDependencies": { 27 | "babel-eslint": "^7.1.0", 28 | "eslint": "^3.6.0", 29 | "eslint-config-airbnb": "^12.0.0", 30 | "eslint-config-roc": "^0.1.0", 31 | "eslint-plugin-import": "^1.16.0", 32 | "eslint-plugin-jsx-a11y": "^2.2.2", 33 | "eslint-plugin-react": "^6.3.0", 34 | "roc-package-web-app-react-dev": "^1.0.0-beta.16", 35 | "roc-plugin-style-sass": "1.0.0-beta.5"{{#if testJest}}, 36 | "roc-plugin-test-jest": "^1.0.0-beta.3" 37 | {{/if}} 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /template/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocjs/roc-template-web-app-react/f831bc6230e6b8ec43561b0f56729f14c56d5882/template/public/favicon.png -------------------------------------------------------------------------------- /template/roc.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | settings: { 3 | runtime: { 4 | applicationName: '{{ title }}', 5 | port: {{ port }}, 6 | serve: ['public', 'build/client'], 7 | favicon: 'favicon.png', 8 | {{#if fetchExample}} 9 | // fetch settings (these are the defaults) 10 | fetch: { 11 | server: ['fetch'], // hook used by server for fetching 12 | client: { 13 | beforeTransition: ['fetch'], // hook used by client for fetching that block route transitions 14 | afterTransition: ['defer', 'deferDone'], // hook used by client for fetching that does not block route transitions 15 | parallel: false, // if we should start non-blocking fetches in parallel with blocking ones 16 | }, 17 | }, 18 | {{/if}} 19 | }, 20 | build: { 21 | {{#if reduxExample}} 22 | reducers: 'src/redux/reducers.js', 23 | {{/if}} 24 | routes: 'src/routes.js', 25 | }, 26 | dev: { 27 | browsersync: { 28 | options: { 29 | open: true, 30 | }, 31 | }, 32 | }, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /template/src/components/clicker/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import { bindActionCreators } from 'redux'; 4 | import { connect } from 'react-redux'; 5 | 6 | import { click } from '../../redux/clicker'; 7 | 8 | import styles from './style.scss'; 9 | 10 | function mapStateToProps(state) { 11 | return { 12 | clicker: state.clicker, 13 | }; 14 | } 15 | 16 | function mapDispatchToProps(dispatch) { 17 | return bindActionCreators({ click }, dispatch); 18 | } 19 | 20 | /* decorate our component to make it redux state aware using react-redux */ 21 | @connect(mapStateToProps, mapDispatchToProps) 22 | export default class Clicker extends React.Component { 23 | static defaultProps = { 24 | clicker: 0, 25 | } 26 | 27 | static propTypes = { 28 | clicker: PropTypes.number.isRequired, 29 | click: PropTypes.func.isRequired, 30 | } 31 | 32 | render() { 33 | return ( 34 | <div className={styles.main}> 35 | <div>Counter: { this.props.clicker }</div> 36 | <button onClick={() => this.props.click(1)}> 37 | +1 38 | </button> 39 | <button onClick={() => this.props.click(-1)}> 40 | -1 41 | </button> 42 | </div> 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /template/src/components/clicker/style.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | margin: 15px 0 15px 0; 3 | } 4 | -------------------------------------------------------------------------------- /template/src/components/fetching/index.js: -------------------------------------------------------------------------------- 1 | // React Hot Reload does not support stateless function components as of now 2 | /* eslint-disable react/prefer-stateless-function, react/no-multi-comp */ 3 | import React, { Component } from 'react'; 4 | import { provideHooks } from 'redial'; 5 | import fetch from 'isomorphic-fetch'; 6 | 7 | import styles from './style.scss'; 8 | 9 | /* functions that promise to fetch json data from GitHub API */ 10 | function fetchRocRepos() { 11 | return fetch('https://api.github.com/users/rocjs/repos') 12 | .then(response => response.json()); 13 | } 14 | 15 | function fetchRocStargazers() { 16 | return fetch('https://api.github.com/repos/rocjs/roc') 17 | .then(response => response.json()); 18 | } 19 | 20 | /* components for simple presentation */ 21 | class RepoList extends Component { 22 | static propTypes = { 23 | display: React.PropTypes.number, 24 | loading: React.PropTypes.bool, 25 | repo: React.PropTypes.arrayOf(React.PropTypes.object), 26 | }; 27 | 28 | static defaultProps = { 29 | display: 5, 30 | repoItems: [], 31 | loading: true, 32 | }; 33 | 34 | render() { 35 | if (this.props.loading) { 36 | return <span>Loading...</span>; 37 | } 38 | 39 | let repoItemsToDisplay = []; 40 | 41 | if (this.props.repo.length > 0) { 42 | repoItemsToDisplay = this.props.repo 43 | .filter((repo, index) => index < this.props.display) 44 | .map(repo => <li key={repo.id}>{repo.name} </li>); 45 | } 46 | 47 | return <ul>{ repoItemsToDisplay }</ul>; 48 | } 49 | } 50 | 51 | class RocStargazers extends Component { 52 | static propTypes = { 53 | loading: React.PropTypes.bool, 54 | stars: React.PropTypes.number, 55 | } 56 | 57 | static defaultProps = { 58 | stars: 0, 59 | loading: true, 60 | }; 61 | 62 | render() { 63 | if (this.props.loading) { 64 | return <span>Loading...</span>; 65 | } 66 | 67 | return <span>{this.props.stars}</span>; 68 | } 69 | } 70 | 71 | class FetchButton extends Component { 72 | static propTypes = { 73 | fetch: React.PropTypes.func, 74 | } 75 | 76 | render() { 77 | return <button onClick={this.props.fetch}>Refetch</button>; 78 | } 79 | } 80 | 81 | /* enhance our component with data fetching abilities */ 82 | @provideHooks({ 83 | // by default, fetch is run on both server and client side 84 | fetch: ({ force, getProps, setProps }) => { 85 | const alreadyFetched = !!getProps().fetch; 86 | 87 | if (force || !alreadyFetched) { 88 | return fetchRocRepos() 89 | .then((json) => { 90 | setProps({ 91 | repo: json, 92 | }); 93 | }); 94 | } 95 | 96 | return Promise.resolve(); 97 | }, 98 | // by default, defer only runs on the client side 99 | defer: ({ setProps }) => fetchRocStargazers() 100 | .then((json) => { 101 | setProps({ 102 | stars: json.stargazers_count, 103 | }); 104 | }), 105 | }) 106 | /* this decorated component is mapped to /fetching/ in routes.js */ 107 | export default class Fetching extends Component { 108 | static propTypes = { 109 | loading: React.PropTypes.bool, 110 | afterTransitionLoading: React.PropTypes.bool, 111 | reload: React.PropTypes.func, 112 | repo: React.PropTypes.arrayOf(React.PropTypes.object), 113 | stars: React.PropTypes.number, 114 | } 115 | 116 | static defaultProps = { 117 | repo: [], 118 | stars: 0, 119 | }; 120 | 121 | render() { 122 | return ( 123 | <div className={styles.main}> 124 | <h1>Data fetching examples</h1> 125 | <p> 126 | We recommend the use of these built-in facilities for data-fetching 127 | connected to your route components. 128 | </p> 129 | <p>This gives you powerful data fetching that works on both the server and the client</p> 130 | 131 | <h2>Universal fetch data (server and client)</h2> 132 | <h3>Top 5 Rocjs repositories</h3> 133 | <RepoList loading={this.props.loading} repo={this.props.repo} /> 134 | 135 | <h2>Clientside fetch data</h2> 136 | <h3>Number of stargazers of rocjs/roc</h3> 137 | <RocStargazers loading={this.props.afterTransitionLoading} stars={this.props.stars} /> 138 | 139 | <h2>Refetch all data</h2> 140 | <FetchButton fetch={this.props.reload} /> 141 | </div> 142 | ); 143 | } 144 | } 145 | /* eslint-enablereact/prefer-stateless-function, react/no-multi-comp */ 146 | -------------------------------------------------------------------------------- /template/src/components/fetching/style.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocjs/roc-template-web-app-react/f831bc6230e6b8ec43561b0f56729f14c56d5882/template/src/components/fetching/style.scss -------------------------------------------------------------------------------- /template/src/components/header/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { IndexLink{{#if fetchExample }}, Link{{else if reduxExample}}, Link{{/if}} } from 'react-router'; 3 | 4 | import logo from './roc.png'; 5 | import styles from './style.scss'; 6 | 7 | export default () => 8 | ( 9 | <div className={styles.header}> 10 | <ul className={styles.menu}> 11 | <li> 12 | <IndexLink 13 | to="/" 14 | className={styles.item} 15 | activeClassName={styles.active} 16 | > 17 | Start 18 | </IndexLink> 19 | </li> 20 | {{#if_eq fetchExample true}} 21 | <li> 22 | <Link 23 | to="/fetching/" 24 | className={styles.item} 25 | activeClassName={styles.active} 26 | > 27 | Data-fetch 28 | </Link> 29 | </li> 30 | {{/if_eq}} 31 | {{#if_eq reduxExample true}} 32 | <li> 33 | <Link 34 | to="/redux/" 35 | className={styles.item} 36 | activeClassName={styles.active} 37 | > 38 | Redux 39 | </Link> 40 | </li> 41 | {{/if_eq}} 42 | </ul> 43 | <img className={styles.logo} src={logo} alt="Logo" /> 44 | </div> 45 | ) 46 | ; 47 | -------------------------------------------------------------------------------- /template/src/components/header/roc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocjs/roc-template-web-app-react/f831bc6230e6b8ec43561b0f56729f14c56d5882/template/src/components/header/roc.png -------------------------------------------------------------------------------- /template/src/components/header/style.scss: -------------------------------------------------------------------------------- 1 | $bright: #fff; 2 | $light: #5f7a91; 3 | $dark: #254763; 4 | 5 | .header { 6 | background: $light; 7 | overflow: auto; 8 | text-align: center; 9 | } 10 | 11 | .menu { 12 | margin: 0 auto 0 auto; 13 | 14 | li { 15 | display: inline-block; 16 | {{#if fetchExample}} 17 | {{#if reduxExample}} 18 | width: 33.3%; 19 | {{else}} 20 | width: 50%; 21 | {{/if}} 22 | {{else}} 23 | {{#if reduxExample}} 24 | width: 50%; 25 | {{else}} 26 | width: 100%; 27 | {{/if}} 28 | {{/if}} 29 | height: 40px; 30 | line-height: 40px; 31 | margin: 0 0 0 0; 32 | } 33 | 34 | li:nth-child(2) { 35 | border-left: 1px solid $bright; 36 | border-right: 1px solid $bright; 37 | } 38 | } 39 | 40 | .item { 41 | display: block; 42 | background: $dark; 43 | border-bottom: 1px solid $bright; 44 | text-transform: uppercase; 45 | 46 | &:hover { 47 | background: $bright; 48 | color: $dark; 49 | border-bottom: none; 50 | } 51 | } 52 | 53 | .active { 54 | background: $bright; 55 | color: $dark; 56 | border-bottom: none; 57 | } 58 | 59 | .logo { 60 | width: 200px; 61 | margin: 20px 0 20px 0; 62 | } 63 | -------------------------------------------------------------------------------- /template/src/components/index.js: -------------------------------------------------------------------------------- 1 | // React Hot Reload does not support stateless function components as of now 2 | /* eslint-disable react/prefer-stateless-function */ 3 | import PropTypes from 'prop-types'; 4 | import React, { Component } from 'react'; 5 | import Helmet from 'react-helmet'; 6 | 7 | import Header from './header'; 8 | import styles from './style.scss'; 9 | 10 | export default class App extends Component { 11 | static propTypes = { 12 | children: PropTypes.node.isRequired, 13 | } 14 | 15 | render() { 16 | return ( 17 | <div> 18 | <Helmet 19 | link={[{ 20 | rel: 'icon', href: '/favicon.png', 21 | }]} 22 | /> 23 | <Header /> 24 | 25 | <div className={styles.main}> 26 | { this.props.children } 27 | </div> 28 | </div> 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /template/src/components/redux/index.js: -------------------------------------------------------------------------------- 1 | // React Hot Reload does not support stateless function components as of now 2 | /* eslint-disable react/prefer-stateless-function */ 3 | import React, { Component } from 'react'; 4 | import { connect } from 'react-redux'; 5 | import { provideHooks } from 'redial'; 6 | import isoFetch from 'isomorphic-fetch'; 7 | 8 | import Clicker from '../../components/clicker'; 9 | import { repoFetchBegin, repoFetchSuccess, repoFetchFail } from '../../redux/repo'; 10 | import styles from './style.scss'; 11 | 12 | const repoApiUrl = 'http://api.github.com/repos/rocjs/roc'; 13 | 14 | function fetchRocStargazers() { 15 | return isoFetch(repoApiUrl) 16 | .then(response => response.json()); 17 | } 18 | 19 | @provideHooks({ 20 | // by default, fetch is run on both server and client side 21 | fetch: ({ dispatch }) => { 22 | // dispatch that fetch has started 23 | dispatch(repoFetchBegin(repoApiUrl)); 24 | 25 | // this "thunk" is compatible with a redux middleware that allows async actions 26 | // https://www.npmjs.com/package/redux-thunk 27 | const fetchThunk = (thunkDispatch) => { 28 | fetchRocStargazers().then((json) => { 29 | thunkDispatch(repoFetchSuccess(repoApiUrl, json)); 30 | }).catch((err) => { 31 | thunkDispatch(repoFetchFail(repoApiUrl, err)); 32 | }); 33 | }; 34 | 35 | // dispatch the thunk that will later dispatch a success of failure action depending on outcome 36 | return dispatch(fetchThunk); 37 | }, 38 | }) 39 | @connect(state => ({ 40 | stargazers: state.repo.stargazers, 41 | })) 42 | export default class Redux extends Component { 43 | static propTypes = { 44 | stargazers: React.PropTypes.number, 45 | } 46 | 47 | render() { 48 | const { stargazers } = this.props; 49 | 50 | return ( 51 | <div className={styles.main}> 52 | <h1>Redux demo</h1> 53 | <p> 54 | Roc takes care of all boilerplate required to set up Redux. <br /> 55 | <strong>Clicker</strong> (components/clicker/index.js) is a simple example-component that 56 | demonstrates how to connect your components to Redux. 57 | </p> 58 | 59 | <Clicker /> 60 | 61 | <p> 62 | You can of course also do your data-fetching by dispatching a 63 | Redux action to do so! We have included a simple demo of this below. 64 | </p> 65 | <p>Number of stargazers (fetched using Redux): <strong>{ stargazers }</strong></p> 66 | </div> 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /template/src/components/redux/style.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | strong { 3 | font-weight: bold; 4 | } 5 | p { 6 | margin-bottom: 20px; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /template/src/components/reset.scss: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | html, body, div, span, applet, object, iframe, 6 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 7 | a, abbr, acronym, address, big, cite, code, 8 | del, dfn, em, img, ins, kbd, q, s, samp, 9 | small, strike, strong, sub, sup, tt, var, 10 | b, u, i, center, 11 | dl, dt, dd, ol, ul, li, 12 | fieldset, form, label, legend, 13 | table, caption, tbody, tfoot, thead, tr, th, td, 14 | article, aside, canvas, details, embed, 15 | figure, figcaption, footer, header, hgroup, 16 | menu, nav, output, ruby, section, summary, 17 | time, mark, audio, video { 18 | margin: 0; 19 | padding: 0; 20 | border: 0; 21 | font-size: 100%; 22 | font: inherit; 23 | vertical-align: baseline; 24 | } 25 | /* HTML5 display-role reset for older browsers */ 26 | article, aside, details, figcaption, figure, 27 | footer, header, hgroup, menu, nav, section { 28 | display: block; 29 | } 30 | body { 31 | line-height: 1; 32 | } 33 | ol, ul { 34 | list-style: none; 35 | } 36 | blockquote, q { 37 | quotes: none; 38 | } 39 | blockquote:before, blockquote:after, 40 | q:before, q:after { 41 | content: ''; 42 | content: none; 43 | } 44 | table { 45 | border-collapse: collapse; 46 | border-spacing: 0; 47 | } 48 | -------------------------------------------------------------------------------- /template/src/components/start/ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocjs/roc-template-web-app-react/f831bc6230e6b8ec43561b0f56729f14c56d5882/template/src/components/start/ext.png -------------------------------------------------------------------------------- /template/src/components/start/index.js: -------------------------------------------------------------------------------- 1 | // React Hot Reload does not support stateless function components as of now 2 | /* eslint-disable react/prefer-stateless-function */ 3 | import React, { Component } from 'react'; 4 | 5 | import ext from './ext.png'; 6 | import styles from './style.scss'; 7 | 8 | export default class Start extends Component { 9 | render() { 10 | return ( 11 | <div className={styles.main}> 12 | <h1>Get started</h1> 13 | <p> 14 | Open <span>src/components/start.js</span> in your favourite editor 15 | to make changes to this page. 16 | </p> 17 | <p><span>src/routes.js</span> maps routes to your components.</p> 18 | 19 | <h2>Docs</h2> 20 | <p> 21 | Run <span>roc docs</span> in your project to generate updated docs for this Roc project. 22 | </p> 23 | 24 | <h2>Learn more</h2> 25 | <img src={ext} className={styles.ext} alt="React & Redux extension" /> 26 | <p> 27 | <a href="https://github.com/rocjs/roc/tree/master/docs#table-of-contents" alt="Docs">Documentation for Roc on Github</a> 28 | </p> 29 | </div> 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /template/src/components/start/index.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | 4 | import Start from './index'; 5 | 6 | jest.mock('./ext.png', () => {}); 7 | jest.mock('./style.scss', () => ({ default: {} })); 8 | 9 | it('renders start correctly', () => { 10 | const tree = renderer.create( 11 | <Start />, 12 | ).toJSON(); 13 | 14 | expect(tree).toMatchSnapshot(); 15 | }); 16 | -------------------------------------------------------------------------------- /template/src/components/start/style.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | text-align: center; 3 | 4 | a { 5 | color: #000; 6 | } 7 | 8 | span { 9 | background: #eee; 10 | padding: 2px; 11 | } 12 | } 13 | 14 | .ext { 15 | width: 250px; 16 | } 17 | -------------------------------------------------------------------------------- /template/src/components/style.scss: -------------------------------------------------------------------------------- 1 | @import "reset"; 2 | 3 | $bright: #fff; 4 | $light: #5f7a91; 5 | $dark: #254763; 6 | 7 | body { 8 | background: $bright; 9 | color: $dark; 10 | font-family: sans-serif; 11 | font-size: 14px; 12 | line-height: 22px; 13 | } 14 | 15 | a { 16 | color: #fff; 17 | text-decoration: none; 18 | } 19 | 20 | h1 { 21 | font-size: 32px; 22 | line-height: 80px; 23 | } 24 | 25 | h2 { 26 | font-size: 26px; 27 | line-height: 52px; 28 | } 29 | 30 | h3 { 31 | font-size: 22px; 32 | line-height: 30px; 33 | } 34 | 35 | button { 36 | padding: 4px; 37 | } 38 | 39 | .main { 40 | background: #fff; 41 | padding: 15px; 42 | max-width: 750px; 43 | margin: 0 auto 0 auto; 44 | } 45 | -------------------------------------------------------------------------------- /template/src/redux/clicker.js: -------------------------------------------------------------------------------- 1 | const CLICKED = 'CLICKED'; 2 | 3 | export default function clicker(state = 0, action = { increment: 1 }) { 4 | if (action.type === CLICKED) { 5 | return state + action.increment; 6 | } 7 | 8 | return state; 9 | } 10 | 11 | export function click(increment) { 12 | return { type: CLICKED, increment }; 13 | } 14 | -------------------------------------------------------------------------------- /template/src/redux/reducers.js: -------------------------------------------------------------------------------- 1 | export clicker from './clicker'; 2 | export repo from './repo'; 3 | -------------------------------------------------------------------------------- /template/src/redux/repo.js: -------------------------------------------------------------------------------- 1 | const FETCH_REPO_REQUEST = 'FETCH_REPO_REQUEST'; 2 | const FETCH_REPO_SUCCESS = 'FETCH_REPO_SUCCESS'; 3 | const FETCH_REPO_FAIL = 'FETCH_REPO_FAIL'; 4 | 5 | const defaultState = { 6 | loading: false, 7 | error: false, 8 | stargazers: undefined, 9 | }; 10 | 11 | export default function repoFetch(state = defaultState, action) { 12 | if (action.type === FETCH_REPO_REQUEST) { 13 | return { 14 | loading: true, 15 | endpoint: action.payload.endpoint, 16 | error: false, 17 | stargazers: undefined, 18 | }; 19 | } 20 | 21 | if (action.type === FETCH_REPO_SUCCESS) { 22 | return { 23 | loading: false, 24 | error: false, 25 | endpoint: action.payload.endpoint, 26 | stargazers: action.payload.data.stargazers_count, 27 | }; 28 | } 29 | 30 | if (action.type === FETCH_REPO_FAIL) { 31 | return { 32 | loading: false, 33 | error: action.payload.error, 34 | endpoint: action.payload.endpoint, 35 | stargazers: undefined, 36 | }; 37 | } 38 | 39 | return state; 40 | } 41 | 42 | export function repoFetchBegin(url) { 43 | return { 44 | type: FETCH_REPO_REQUEST, 45 | payload: { 46 | endpoint: url, 47 | }, 48 | }; 49 | } 50 | 51 | export function repoFetchSuccess(url, json) { 52 | return { 53 | type: FETCH_REPO_SUCCESS, 54 | payload: { 55 | endpoint: url, 56 | data: json, 57 | }, 58 | }; 59 | } 60 | 61 | export function repoFetchFail(url, err) { 62 | return { 63 | type: FETCH_REPO_FAIL, 64 | payload: { 65 | error: err, 66 | endpoint: url, 67 | }, 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /template/src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | 4 | import App from './components'; 5 | import Start from './components/start'; 6 | {{#if_eq fetchExample true}} 7 | import Fetching from './components/fetching'; 8 | {{/if_eq}} 9 | {{#if_eq reduxExample true}} 10 | import Redux from './components/redux'; 11 | {{/if_eq}} 12 | 13 | export default () => ( 14 | <Route component={App}> 15 | <IndexRoute component={Start} /> 16 | {{#if_eq fetchExample true}} 17 | <Route component={Fetching} path="/fetching/" /> 18 | {{/if_eq}} 19 | {{#if_eq reduxExample true}} 20 | <Route component={Redux} path="/redux/" /> 21 | {{/if_eq}} 22 | </Route> 23 | ); 24 | --------------------------------------------------------------------------------