├── .babelrc ├── src ├── components │ ├── WelcomeMessage.scss │ └── WelcomeMessage.js ├── store │ ├── configureStore.js │ ├── configureStore.prod.js │ └── configureStore.dev.js ├── layouts │ └── Core.js ├── reducers │ ├── index.js │ └── messages.js ├── views │ ├── Home.scss │ ├── Home.js │ ├── InsertMessage.scss │ └── InsertMessage.js ├── actions │ └── messages.js ├── routes │ └── index.js ├── helpers │ └── editor.js ├── containers │ └── Root.js └── index.js ├── public ├── assets │ ├── logo.png │ ├── spinner.gif │ ├── logo-small.png │ ├── main.css │ ├── logo.svg │ ├── icon_ticket_editor.svg │ └── index.html ├── translations │ └── en.json └── manifest.json ├── Procfile ├── config ├── jest │ ├── fileTransform.js │ └── cssTransform.js ├── polyfills.js ├── env.js ├── paths.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── .gitignore ├── scripts ├── test.js └── start.js ├── README.md ├── package.json └── LICENSE /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets" : ["es2015", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /src/components/WelcomeMessage.scss: -------------------------------------------------------------------------------- 1 | .welcome { 2 | flex: 1; 3 | font-weight: bold; 4 | } 5 | -------------------------------------------------------------------------------- /public/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudapp/zendesk-react-redux-boilerplate/HEAD/public/assets/logo.png -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | reload: webpack --watch --config ./config/webpack.config.dev.js 2 | server: zat server --path=public --unattended 3 | -------------------------------------------------------------------------------- /public/assets/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudapp/zendesk-react-redux-boilerplate/HEAD/public/assets/spinner.gif -------------------------------------------------------------------------------- /public/assets/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudapp/zendesk-react-redux-boilerplate/HEAD/public/assets/logo-small.png -------------------------------------------------------------------------------- /src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./configureStore.prod'); 3 | } else { 4 | module.exports = require('./configureStore.dev'); 5 | } 6 | -------------------------------------------------------------------------------- /public/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "_warning": "AUTOMATICALLY GENERATED FROM src/translations/en.json - DO NOT MODIFY THIS FILE DIRECTLY", 3 | "app": { 4 | "name": "add your app name here", 5 | "description": "add your app description here" 6 | } 7 | } -------------------------------------------------------------------------------- /src/layouts/Core.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function CoreLayout ({ children }) { 4 | return ( 5 |
6 | {children} 7 |
8 | ); 9 | } 10 | 11 | CoreLayout.propTypes = { 12 | children: React.PropTypes.element 13 | }; 14 | 15 | export default CoreLayout; 16 | -------------------------------------------------------------------------------- /src/components/WelcomeMessage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import styles from './WelcomeMessage.scss'; 4 | 5 | const WelcomeMessage = () => { 6 | return ( 7 |
8 | Welcome! 9 |
10 | ); 11 | } 12 | 13 | export default WelcomeMessage; 14 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | 4 | import messages from './messages'; 5 | 6 | const rootReducer = combineReducers({ 7 | messages, 8 | routing: routerReducer, 9 | }); 10 | 11 | export default rootReducer; 12 | -------------------------------------------------------------------------------- /src/store/configureStore.prod.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import rootReducer from '../reducers'; 4 | 5 | const configureStore = preloadedState => createStore( 6 | rootReducer, 7 | preloadedState, 8 | applyMiddleware(thunk), 9 | ); 10 | 11 | export default configureStore; 12 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | // This is a custom Jest transformer turning file imports into filenames. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process(src, filename) { 8 | return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | // This is a custom Jest transformer turning style imports into empty objects. 2 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 3 | 4 | module.exports = { 5 | process() { 6 | return 'module.exports = {};'; 7 | }, 8 | getCacheKey(fileData, filename) { 9 | // The output is always the same. 10 | return 'cssTransform'; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Zendesk Ticket Editor App", 3 | "author": { 4 | "name": "Jane Doe", 5 | "email": "jane@doe.com", 6 | "url": "foobar.com" 7 | }, 8 | "defaultLocale": "en", 9 | "private": true, 10 | "location": { 11 | "support": { 12 | "ticket_editor": "assets/index.html" 13 | } 14 | }, 15 | "version": "1.0", 16 | "frameworkVersion": "2.0" 17 | } 18 | -------------------------------------------------------------------------------- /src/views/Home.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | display: -webkit-flex; 3 | display: flex; 4 | -webkit-flex-direction: column; 5 | flex-direction: column; 6 | -webkit-align-items: center; 7 | align-items: center; 8 | -webkit-justify-content: center; 9 | justify-content: center; 10 | font-family: Open Sans,Helvetica Neue,Helvetica,sans-serif; 11 | padding: 50px; 12 | } 13 | 14 | .toMessage { 15 | order: 1; 16 | flex: 1; 17 | padding: 10px; 18 | } 19 | -------------------------------------------------------------------------------- /public/assets/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0px !important; 3 | font-family: Open Sans,Helvetica Neue,Helvetica,Arial,Geneva,sans-serif; 4 | font-size: 13px; 5 | } 6 | 7 | ::-webkit-scrollbar { 8 | width: 0px; /* remove scrollbar space */ 9 | background: transparent; /* optional: just make scrollbar invisible */ 10 | } 11 | /* optional: show position indicator in red */ 12 | ::-webkit-scrollbar-thumb { 13 | background: #FF0000; 14 | } 15 | 16 | .muted { 17 | color: #989fa9; 18 | } 19 | -------------------------------------------------------------------------------- /src/reducers/messages.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions'; 2 | 3 | const defaultState = { 4 | resultMessage: '', 5 | hasSuccess: false, 6 | }; 7 | 8 | const messages = handleActions({ 9 | INSERT_MESSAGE: (state, action) => ({ 10 | hasSuccess: true, 11 | resultMessage: 'success!', 12 | }), 13 | MESSAGE_FAILED: (state, action) => ({ 14 | hasSuccess: false, 15 | resultMessage: 'failed (are you outside of zendesk?)', 16 | }) 17 | }, defaultState); 18 | 19 | export default messages; 20 | -------------------------------------------------------------------------------- /src/views/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | import WelcomeMessage from '../components/WelcomeMessage.js'; 5 | import styles from './Home.scss'; 6 | 7 | 8 | export class HomeView extends Component { 9 | 10 | render () { 11 | return ( 12 |
13 | 14 | Navigate to Insert Message 15 |
16 | ); 17 | } 18 | } 19 | 20 | export default HomeView 21 | -------------------------------------------------------------------------------- /src/actions/messages.js: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions'; 2 | 3 | import TicketEditor from '../helpers/editor'; 4 | 5 | 6 | export const INSERT_MESSAGE = 'INSERT_MESSAGE'; 7 | export const MESSAGE_FAILED = 'MESSAGE_FAILED'; 8 | 9 | export const putMessage = createAction(INSERT_MESSAGE); 10 | export const failMessage = createAction(MESSAGE_FAILED); 11 | 12 | export const insertMessage = (message) => (dispatch, getState) => { 13 | TicketEditor.insertMessage( 14 | message, 15 | () => dispatch(putMessage()), 16 | () => dispatch(failMessage()) 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | 4 | import CoreLayout from '../layouts/Core.js'; 5 | 6 | import InsertMessageView from '../views/InsertMessage.js'; 7 | import HomeView from '../views/Home.js'; 8 | 9 | export default ( 10 | 11 | 12 | 13 | // This is required for zendesk to route correctly. 14 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /src/helpers/editor.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | import { client } from '../index.js'; 4 | 5 | const ERROR_MESSAGE = 'cannot use the client without zendesk'; 6 | const INSERT = 'ticket.editor.insert'; 7 | 8 | export default class TicketEditor { 9 | 10 | static insertMessage(message, success, failure) { 11 | try { 12 | client.invoke(INSERT, message); 13 | if (_.isFunction(success)) { 14 | success(); 15 | } 16 | } catch(error) { 17 | if (_.isFunction(failure)) { 18 | console.log(ERROR_MESSAGE); 19 | failure(); 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | if (typeof Promise === 'undefined') { 2 | // Rejection tracking prevents a common issue where React gets into an 3 | // inconsistent state due to an error, but it gets swallowed by a Promise, 4 | // and the user has no idea what causes React's erratic future behavior. 5 | require('promise/lib/rejection-tracking').enable(); 6 | window.Promise = require('promise/lib/es6-extensions.js'); 7 | } 8 | 9 | // fetch() polyfill for making API calls. 10 | require('whatwg-fetch'); 11 | 12 | // Object.assign() is commonly used with React. 13 | // It will use the native implementation if it's present and isn't buggy. 14 | Object.assign = require('object-assign'); 15 | -------------------------------------------------------------------------------- /src/views/InsertMessage.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | display: -webkit-flex; 3 | display: flex; 4 | -webkit-flex-direction: column; 5 | flex-direction: column; 6 | -webkit-align-items: center; 7 | align-items: center; 8 | -webkit-justify-content: center; 9 | justify-content: center; 10 | font-family: Open Sans,Helvetica Neue,Helvetica,sans-serif; 11 | padding: 50px; 12 | } 13 | 14 | .toHome { 15 | order: 1; 16 | padding: 10px; 17 | } 18 | 19 | .instructions { 20 | order: 2; 21 | font-size: 16px; 22 | padding: 10px; 23 | } 24 | 25 | .button { 26 | order: 3; 27 | } 28 | 29 | .success { 30 | order: 4; 31 | font-weight: bold; 32 | font-size: 20px; 33 | padding: 10px; 34 | } 35 | -------------------------------------------------------------------------------- /public/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /src/store/configureStore.dev.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import promiseMiddleware from 'redux-promise'; 4 | import rootReducer from '../reducers'; 5 | 6 | const configureStore = preloadedState => { 7 | const store = createStore( 8 | rootReducer, 9 | preloadedState, 10 | applyMiddleware(thunk, promiseMiddleware), 11 | ); 12 | 13 | if (module.hot) { 14 | // Enable Webpack hot module replacement for reducers 15 | module.hot.accept('../reducers', () => { 16 | const nextRootReducer = require('../reducers').default; 17 | store.replaceReducer(nextRootReducer); 18 | }); 19 | } 20 | 21 | return store; 22 | }; 23 | 24 | export default configureStore; 25 | -------------------------------------------------------------------------------- /public/assets/icon_ticket_editor.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /public/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | dist/assets 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | public/assets/*.hot-update.js* 41 | 42 | public/assets/index.js 43 | 44 | public/assets/index.js.map 45 | -------------------------------------------------------------------------------- /src/containers/Root.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { Router } from 'react-router'; 4 | 5 | export default class Root extends Component { 6 | 7 | createElement(Component, props) { 8 | return ; 9 | } 10 | 11 | get content() { 12 | return ( 13 | 14 | { this.props.routes } 15 | 16 | ); 17 | } 18 | 19 | render() { 20 | return ( 21 | 22 |
23 | { this.content } 24 |
25 |
26 | ); 27 | } 28 | } 29 | 30 | Root.propTypes = { 31 | history: React.PropTypes.object.isRequired, 32 | routes: React.PropTypes.element.isRequired, 33 | store: React.PropTypes.object.isRequired 34 | }; 35 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test'; 2 | process.env.PUBLIC_URL = ''; 3 | 4 | // Load environment variables from .env file. Suppress warnings using silent 5 | // if this file is missing. dotenv will never modify any environment variables 6 | // that have already been set. 7 | // https://github.com/motdotla/dotenv 8 | require('dotenv').config({silent: true}); 9 | 10 | const jest = require('jest'); 11 | const argv = process.argv.slice(2); 12 | 13 | // Watch unless on CI or in coverage mode 14 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 15 | argv.push('--watch'); 16 | } 17 | 18 | // A temporary hack to clear terminal correctly. 19 | // You can remove this after updating to Jest 18 when it's out. 20 | // https://github.com/facebook/jest/pull/2230 21 | var realWrite = process.stdout.write; 22 | var CLEAR = process.platform === 'win32' ? '\x1Bc' : '\x1B[2J\x1B[3J\x1B[H'; 23 | process.stdout.write = function(chunk, encoding, callback) { 24 | if (chunk === '\x1B[2J\x1B[H') { 25 | chunk = CLEAR; 26 | } 27 | return realWrite.call(this, chunk, encoding, callback); 28 | }; 29 | 30 | 31 | jest.run(argv); 32 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { createMemoryHistory } from 'react-router'; 4 | import { syncHistoryWithStore } from 'react-router-redux'; 5 | import configureStore from './store/configureStore'; 6 | import Root from './containers/Root.js'; 7 | import routes from './routes'; 8 | import ZAFClient from 'zendesk_app_framework_sdk'; 9 | 10 | var client = ZAFClient.init(); 11 | 12 | const rootElement = document.getElementById('root'); 13 | 14 | // Zendesk hosting does not allow for urls to be manipulated; we need this here.s 15 | const memoryHistory = createMemoryHistory(); 16 | 17 | // client.invoke is some magical methods that is only available exists in the zendesk environment. 18 | try { 19 | client.invoke('resize', {width: '600px', height: '320px'}); 20 | } catch(e) { 21 | console.log('cannot resize zendesk client when developing outside of zendesk'); 22 | } 23 | 24 | const store = configureStore(window.__PRELOADED_STATE__); 25 | const history = syncHistoryWithStore(memoryHistory, store); 26 | 27 | render( 28 | , 29 | rootElement 30 | ); 31 | 32 | export { client, memoryHistory }; 33 | -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 2 | // injected into the application via DefinePlugin in Webpack configuration. 3 | 4 | var REACT_APP = /^REACT_APP_/i; 5 | 6 | function getClientEnvironment(publicUrl) { 7 | var processEnv = Object 8 | .keys(process.env) 9 | .filter(key => REACT_APP.test(key)) 10 | .reduce((env, key) => { 11 | env[key] = JSON.stringify(process.env[key]); 12 | return env; 13 | }, { 14 | // Useful for determining whether we’re running in production mode. 15 | // Most importantly, it switches React into the correct mode. 16 | 'NODE_ENV': JSON.stringify( 17 | process.env.NODE_ENV || 'development' 18 | ), 19 | // Useful for resolving the correct path to static assets in `public`. 20 | // For example, . 21 | // This should only be used as an escape hatch. Normally you would put 22 | // images into the `src` and `import` them in code to get their paths. 23 | 'PUBLIC_URL': JSON.stringify(publicUrl) 24 | }); 25 | return {'process.env': processEnv}; 26 | } 27 | 28 | module.exports = getClientEnvironment; 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zendesk-react-redux-boilerplate 2 | Boilerplate for a Zendesk ticket editor app using react and redux. Inspired by Zendesk's [app scaffold](https://github.com/zendesk/app_scaffold). Please refer to that project for help on developing and deploying Zendesk apps. 3 | 4 | Some notables include: 5 | - basic client-side routing via redux-router 6 | - history, routes and store as part of base application component 7 | - (sass|scss)-loader for css-modules 8 | - action handlers and dispatchers via redux-actions 9 | - ticket editor helper for interfacing with ZD's ticket editor 10 | - hot code reloading 11 | - development in and out of Zendesk ticket environment 12 | 13 | ## Developing Locally 14 | Download the source and `npm install`. 15 | 16 | ### Developing outside of Zendesk 17 | If you want to develop outside of a Zendesk ticket environment (`localhost:4000`), run: 18 | ```bash 19 | npm run start 20 | ``` 21 | 22 | This is useful for developing `js` or `(sass|scss)` that do not interface directly with the `ZAF` client; this project uses a hot reloader for tracking code changes. I found refreshing my apps in the ticket environment tedious for most of my changes. 23 | 24 | ### Developing inside of Zendesk 25 | If you want to develop inside of a Zendesk ticket environment (`.../agent/tickets/?zat=true`), run: 26 | ```bash 27 | nf start 28 | ``` 29 | 30 | You can have both of the above commands running at the same time (they serve on different ports). 31 | -------------------------------------------------------------------------------- /src/views/InsertMessage.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Link } from 'react-router'; 4 | 5 | 6 | import { insertMessage } from '../actions/messages.js'; 7 | 8 | import styles from './InsertMessage.scss'; 9 | 10 | export class InsertMessageView extends Component { 11 | 12 | render () { 13 | return ( 14 |
15 | Go Home 16 |
Press the button to insert into the ticket editor.
17 | 20 | { this.props.hasSuccess && 21 |
{ this.props.resultMessage }
22 | } 23 | { !this.props.hasSuccess && 24 |
{ this.props.resultMessage }
25 | } 26 |
27 | ); 28 | } 29 | } 30 | 31 | InsertMessageView.propTypes = { 32 | insertMessage: PropTypes.func, 33 | resultMessage: PropTypes.string, 34 | hasSuccess: PropTypes.bool, 35 | }; 36 | 37 | const mapStateToProps = state => ({ 38 | resultMessage: state.successMessage, 39 | hasSuccess: state.hasSuccess, 40 | }); 41 | 42 | const mapDispatchToProps = (dispatch, ownProps) => ({ 43 | insertMessage: () => dispatch(insertMessage('hello world')), 44 | }) 45 | 46 | InsertMessageView.contextTypes = { 47 | store: PropTypes.any, 48 | }; 49 | 50 | export default connect(mapStateToProps, mapDispatchToProps)(InsertMessageView); 51 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | 4 | // Make sure any symlinks in the project folder are resolved: 5 | // https://github.com/facebookincubator/create-react-app/issues/637 6 | var appDirectory = fs.realpathSync(process.cwd()); 7 | function resolveApp(relativePath) { 8 | return path.resolve(appDirectory, relativePath); 9 | } 10 | 11 | // We support resolving modules according to `NODE_PATH`. 12 | // This lets you use absolute paths in imports inside large monorepos: 13 | // https://github.com/facebookincubator/create-react-app/issues/253. 14 | 15 | // It works similar to `NODE_PATH` in Node itself: 16 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 17 | 18 | // We will export `nodePaths` as an array of absolute paths. 19 | // It will then be used by Webpack configs. 20 | // Jest doesn’t need this because it already handles `NODE_PATH` out of the box. 21 | 22 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 23 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 24 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 25 | 26 | var nodePaths = (process.env.NODE_PATH || '') 27 | .split(process.platform === 'win32' ? ';' : ':') 28 | .filter(Boolean) 29 | .filter(folder => !path.isAbsolute(folder)) 30 | .map(resolveApp); 31 | 32 | // config after eject: we're in ./config/ 33 | module.exports = { 34 | appBuild: resolveApp('public/assets'), 35 | appPublic: resolveApp('public/assets'), 36 | appHtml: resolveApp('public/assets/index.html'), 37 | appIndexJs: resolveApp('src/index.js'), 38 | appPackageJson: resolveApp('package.json'), 39 | appSrc: resolveApp('src'), 40 | yarnLockFile: resolveApp('yarn.lock'), 41 | testsSetup: resolveApp('src/setupTests.js'), 42 | appNodeModules: resolveApp('node_modules'), 43 | ownNodeModules: resolveApp('node_modules'), 44 | nodePaths: nodePaths 45 | }; 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "addname", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "autoprefixer": "6.5.1", 7 | "babel-core": "6.17.0", 8 | "babel-eslint": "7.1.1", 9 | "babel-jest": "17.0.2", 10 | "babel-loader": "6.2.7", 11 | "babel-preset-react-app": "^2.0.1", 12 | "babel-preset-stage-1": "^6.16.0", 13 | "case-sensitive-paths-webpack-plugin": "1.1.4", 14 | "chalk": "1.1.3", 15 | "connect-history-api-fallback": "1.3.0", 16 | "cross-spawn": "4.0.2", 17 | "css-loader": "0.26.0", 18 | "detect-port": "1.0.1", 19 | "dotenv": "2.0.0", 20 | "eslint": "3.8.1", 21 | "eslint-config-react-app": "^0.5.0", 22 | "eslint-loader": "1.6.0", 23 | "eslint-plugin-flowtype": "2.21.0", 24 | "eslint-plugin-import": "2.0.1", 25 | "eslint-plugin-jsx-a11y": "2.2.3", 26 | "eslint-plugin-react": "6.4.1", 27 | "extract-text-webpack-plugin": "1.0.1", 28 | "file-loader": "0.9.0", 29 | "filesize": "3.3.0", 30 | "fs-extra": "0.30.0", 31 | "gzip-size": "3.0.0", 32 | "html-webpack-plugin": "2.24.0", 33 | "http-proxy-middleware": "0.17.2", 34 | "jest": "17.0.2", 35 | "json-loader": "0.5.4", 36 | "node-sass": "^4.3.0", 37 | "object-assign": "4.1.0", 38 | "path-exists": "2.1.0", 39 | "promise": "7.1.1", 40 | "react-dev-utils": "^0.4.2", 41 | "recursive-readdir": "2.1.0", 42 | "sass-loader": "^4.1.1", 43 | "strip-ansi": "3.0.1", 44 | "style-loader": "0.13.1", 45 | "url-loader": "0.5.7", 46 | "webpack": "1.14.0", 47 | "webpack-dev-server": "1.16.2", 48 | "webpack-manifest-plugin": "1.1.0", 49 | "whatwg-fetch": "1.0.0" 50 | }, 51 | "dependencies": { 52 | "lodash": "^4.17.4", 53 | "react": "^15.4.2", 54 | "react-dom": "^15.4.2", 55 | "react-redux": "^5.0.2", 56 | "react-router": "^3.0.0", 57 | "react-router-bootstrap": "^0.23.1", 58 | "react-router-redux": "^4.0.7", 59 | "redux": "^3.6.0", 60 | "redux-actions": "^1.2.0", 61 | "redux-promise": "^0.5.3", 62 | "redux-thunk": "^2.1.0", 63 | "storage-available": "^1.0.0" 64 | }, 65 | "scripts": { 66 | "start": "node scripts/start.js", 67 | "test": "node scripts/test.js --env=jsdom" 68 | }, 69 | "jest": { 70 | "collectCoverageFrom": [ 71 | "src/**/*.{js,jsx}" 72 | ], 73 | "setupFiles": [ 74 | "/config/polyfills.js" 75 | ], 76 | "testPathIgnorePatterns": [ 77 | "[/\\\\](build|docs|node_modules)[/\\\\]" 78 | ], 79 | "testEnvironment": "node", 80 | "testURL": "http://localhost", 81 | "transform": { 82 | "^.+\\.(js|jsx)$": "/node_modules/babel-jest", 83 | "^.+\\.css$": "/config/jest/cssTransform.js", 84 | "^(?!.*\\.(js|jsx|css|json)$)": "/config/jest/fileTransform.js" 85 | }, 86 | "transformIgnorePatterns": [ 87 | "[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$" 88 | ], 89 | "moduleNameMapper": { 90 | "^react-native$": "react-native-web" 91 | } 92 | }, 93 | "babel": { 94 | "presets": [ 95 | "react-app" 96 | ] 97 | }, 98 | "eslintConfig": { 99 | "extends": "react-app" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /config/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | var autoprefixer = require('autoprefixer'); 2 | var webpack = require('webpack'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); 5 | var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); 6 | var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); 7 | var getClientEnvironment = require('./env'); 8 | var paths = require('./paths'); 9 | 10 | 11 | // Webpack uses `publicPath` to determine where the app is being served from. 12 | // In development, we always serve from the root. This makes config easier. 13 | var publicPath = '/'; 14 | // `publicUrl` is just like `publicPath`, but we will provide it to our app 15 | // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. 16 | // Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz. 17 | var publicUrl = ''; 18 | // Get environment variables to inject into our app. 19 | var env = getClientEnvironment(publicUrl); 20 | 21 | // This is the development configuration. 22 | // It is focused on developer experience and fast rebuilds. 23 | // The production configuration is different and lives in a separate file. 24 | module.exports = { 25 | // You may want 'eval' instead if you prefer to see the compiled output in DevTools. 26 | // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343. 27 | devtool: 'cheap-module-source-map', 28 | // These are the "entry points" to our application. 29 | // This means they will be the "root" imports that are included in JS bundle. 30 | // The first two entry points enable "hot" CSS and auto-refreshes for JS. 31 | entry: [ 32 | // Include an alternative client for WebpackDevServer. A client's job is to 33 | // connect to WebpackDevServer by a socket and get notified about changes. 34 | // When you save a file, the client will either apply hot updates (in case 35 | // of CSS changes), or refresh the page (in case of JS changes). When you 36 | // make a syntax error, this client will display a syntax error overlay. 37 | // Note: instead of the default WebpackDevServer client, we use a custom one 38 | // to bring better experience for Create React App users. You can replace 39 | // the line below with these two lines if you prefer the stock client: 40 | // require.resolve('webpack-dev-server/client') + '?/', 41 | // require.resolve('webpack/hot/dev-server'), 42 | require.resolve('react-dev-utils/webpackHotDevClient'), 43 | // We ship a few polyfills by default: 44 | require.resolve('./polyfills'), 45 | // Finally, this is your app's code: 46 | paths.appIndexJs 47 | // We include the app code last so that if there is a runtime error during 48 | // initialization, it doesn't blow up the WebpackDevServer client, and 49 | // changing JS code would still trigger a refresh. 50 | ], 51 | externals: { 52 | zendesk_app_framework_sdk: 'ZAFClient' 53 | }, 54 | output: { 55 | // Next line is not used in dev but WebpackDevServer crashes without it: 56 | path: paths.appBuild, 57 | // Add /* filename */ comments to generated require()s in the output. 58 | pathinfo: true, 59 | // This does not produce a real file. It's just the virtual path that is 60 | // served by WebpackDevServer in development. This is the JS bundle 61 | // containing code from all our entry points, and the Webpack runtime. 62 | filename: 'index.js', 63 | // This is the URL that app is served from. We use "/" in development. 64 | publicPath: publicPath 65 | }, 66 | resolve: { 67 | // This allows you to set a fallback for where Webpack should look for modules. 68 | // We read `NODE_PATH` environment variable in `paths.js` and pass paths here. 69 | // We use `fallback` instead of `root` because we want `node_modules` to "win" 70 | // if there any conflicts. This matches Node resolution mechanism. 71 | // https://github.com/facebookincubator/create-react-app/issues/253 72 | fallback: paths.nodePaths, 73 | // These are the reasonable defaults supported by the Node ecosystem. 74 | // We also include JSX as a common component filename extension to support 75 | // some tools, although we do not recommend using it, see: 76 | // https://github.com/facebookincubator/create-react-app/issues/290 77 | extensions: ['.js', '.json', '.jsx', ''], 78 | alias: { 79 | // Support React Native Web 80 | // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 81 | 'react-native': 'react-native-web' 82 | } 83 | }, 84 | module: { 85 | // First, run the linter. 86 | // It's important to do this before Babel processes the JS. 87 | preLoaders: [ 88 | { 89 | test: /\.(js|jsx)$/, 90 | loader: 'eslint', 91 | include: paths.appSrc, 92 | } 93 | ], 94 | loaders: [ 95 | // Default loader: load all assets that are not handled 96 | // by other loaders with the url loader. 97 | // Note: This list needs to be updated with every change of extensions 98 | // the other loaders match. 99 | // E.g., when adding a loader for a new supported file extension, 100 | // we need to add the supported extension to this loader too. 101 | // Add one new line in `exclude` for each loader. 102 | // 103 | // "file" loader makes sure those assets get served by WebpackDevServer. 104 | // When you `import` an asset, you get its (virtual) filename. 105 | // In production, they would get copied to the `build` folder. 106 | // "url" loader works like "file" loader except that it embeds assets 107 | // smaller than specified limit in bytes as data URLs to avoid requests. 108 | // A missing `test` is equivalent to a match. 109 | { 110 | exclude: [ 111 | /\.html$/, 112 | /\.(js|jsx)$/, 113 | /\.css$/, 114 | /\.json$/, 115 | /\.(sass|scss)$/ 116 | ], 117 | loader: 'url', 118 | query: { 119 | limit: 10000, 120 | name: 'static/media/[name].[hash:8].[ext]' 121 | } 122 | }, 123 | // Process JS with Babel. 124 | { 125 | test: /\.(js|jsx)$/, 126 | include: paths.appSrc, 127 | loader: 'babel', 128 | query: { 129 | // This is a feature of `babel-loader` for webpack (not Babel itself). 130 | // It enables caching results in ./node_modules/.cache/babel-loader/ 131 | // directory for faster rebuilds. 132 | presets: ['react', 'es2015', 'stage-1'], 133 | plugins: ['transform-class-properties', 'transform-runtime'], 134 | cacheDirectory: true 135 | } 136 | }, 137 | // "postcss" loader applies autoprefixer to our CSS. 138 | // "css" loader resolves paths in CSS and adds assets as dependencies. 139 | // "style" loader turns CSS into JS modules that inject