├── .DS_Store ├── .babelrc ├── .gitignore ├── LICENSE.md ├── README.md ├── index.html ├── karma.conf.js ├── main.js ├── model └── todo.js ├── package-lock.json ├── package.json ├── renderer.js ├── server.js ├── src ├── electron-starter.js ├── registerServiceWorker.js ├── style.js └── todos │ ├── actions │ ├── index-test.js │ └── index.js │ ├── components │ ├── App.js │ ├── Todo.js │ ├── TodoBoard.js │ ├── TodoList-test.js │ └── TodoList.js │ ├── containers │ ├── TodoContainer.js │ ├── TodoFormContainer.js │ └── TodoListContainer.js │ ├── reducers │ ├── index.js │ ├── todos-test.js │ └── todos.js │ └── store │ ├── configureStore.dev.js │ ├── configureStore.js │ └── configureStore.prod.js ├── tests.webpack.js └── webpack.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amelieoller/electron-reactjs-mongo-express/e09fc9f3fc455a6d1826656e8e4a8335850b69dd/.DS_Store -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", "react" 4 | ], 5 | "plugins": ["transform-object-rest-spread"] 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea/ 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | ================== 3 | 4 | Statement of Purpose 5 | --------------------- 6 | 7 | The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). 8 | 9 | Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. 10 | 11 | For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 12 | 13 | 1. Copyright and Related Rights. 14 | -------------------------------- 15 | A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: 16 | 17 | i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; 18 | ii. moral rights retained by the original author(s) and/or performer(s); 19 | iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; 20 | iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; 21 | v. rights protecting the extraction, dissemination, use and reuse of data in a Work; 22 | vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and 23 | vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 24 | 25 | 2. Waiver. 26 | ----------- 27 | To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 28 | 29 | 3. Public License Fallback. 30 | ---------------------------- 31 | Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 32 | 33 | 4. Limitations and Disclaimers. 34 | -------------------------------- 35 | 36 | a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. 37 | b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. 38 | c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. 39 | d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Electron-ReactJS-Node-Mongo app 2 | 3 | **Clone and run for a quick way to see Electron in action.** 4 | 5 | This is an Electron application written in ReactJS that implements a ToDo list that is stored in a mongoDB. 6 | 7 | 8 | ## Installation 9 | 10 | To clone and run this repository you'll need [Git](https://git-scm.com) and [Node.js](https://nodejs.org/en/download/) (which comes with [npm](http://npmjs.com)) installed on your computer. From your command line: 11 | 12 | ```bash 13 | # Install Node & MongoDB 14 | # Clone this repository 15 | $ git clone https://github.com/jwncoexists/electron-reactjs-mongo-express 16 | # Go into the repository 17 | $ cd electron-reactjs-mongo-express 18 | # Install dependencies 19 | $ npm install 20 | # Start up MongoDB 21 | $ mongod 22 | 23 | ``` 24 | 25 | ## To Use 26 | 27 | # Run the app 28 | $ npm run start 29 | 30 | # Run the tests 31 | $ npm run test 32 | 33 | ## License 34 | 35 | MIT 36 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | To-Do APP With Electron, React, Mongo, Express 6 | 7 | 8 | 9 | 10 |
11 | Loading... 12 |
13 | 14 | 15 | 33 | 38 | 39 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // karma.conf.js 2 | var webpack = require('webpack'); 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | browsers: ['Chrome'], 7 | singleRun: true, 8 | frameworks: ['jasmine'], 9 | files: [ 10 | 'tests.webpack.js' 11 | ], 12 | preprocessors: { 13 | 'tests.webpack.js': ['webpack'] 14 | }, 15 | reporters: ['dots'], 16 | webpack: { 17 | module: { 18 | loaders: [ 19 | {test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader'} 20 | ] 21 | }, 22 | watch: true 23 | }, 24 | webpackServer: { 25 | noInfo: true 26 | } 27 | }); 28 | }; -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron') 2 | // Module to control application life. 3 | const app = electron.app 4 | // Module to create native browser window. 5 | const BrowserWindow = electron.BrowserWindow 6 | 7 | const path = require('path') 8 | const url = require('url') 9 | 10 | // Keep a global reference of the window object, if you don't, the window will 11 | // be closed automatically when the JavaScript object is garbage collected. 12 | let mainWindow 13 | 14 | function createWindow () { 15 | // Create the browser window. 16 | mainWindow = new BrowserWindow({width: 800, height: 600}) 17 | 18 | // and load the index.html of the app. 19 | mainWindow.loadURL(url.format({ 20 | pathname: path.join(__dirname, 'index.html'), 21 | protocol: 'file:', 22 | slashes: true 23 | })) 24 | 25 | // Open the DevTools. 26 | // mainWindow.webContents.openDevTools() 27 | 28 | // Emitted when the window is closed. 29 | mainWindow.on('closed', function () { 30 | // Dereference the window object, usually you would store windows 31 | // in an array if your app supports multi windows, this is the time 32 | // when you should delete the corresponding element. 33 | mainWindow = null 34 | }) 35 | } 36 | 37 | // This method will be called when Electron has finished 38 | // initialization and is ready to create browser windows. 39 | // Some APIs can only be used after this event occurs. 40 | app.on('ready', createWindow) 41 | 42 | // Quit when all windows are closed. 43 | app.on('window-all-closed', function () { 44 | // On OS X it is common for applications and their menu bar 45 | // to stay active until the user quits explicitly with Cmd + Q 46 | if (process.platform !== 'darwin') { 47 | app.quit() 48 | } 49 | }) 50 | 51 | app.on('activate', function () { 52 | // On OS X it's common to re-create a window in the app when the 53 | // dock icon is clicked and there are no other windows open. 54 | if (mainWindow === null) { 55 | createWindow() 56 | } 57 | }) 58 | 59 | // In this file you can include the rest of your app's specific main process 60 | // code. You can also put them in separate files and require them here. 61 | -------------------------------------------------------------------------------- /model/todo.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | //import dependency 4 | var mongoose = require('mongoose'); 5 | var Schema = mongoose.Schema; 6 | //create new instance of the mongoose.schema. the schema takes an 7 | //object that shows the shape of your database entries. 8 | var TodoSchema = new Schema({ 9 | title: {type: String, required: true}, 10 | completed: {type: Boolean, default: false} 11 | }); 12 | //export our module to use in server.js 13 | module.exports = mongoose.model('Todo', TodoSchema); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-mern-app", 3 | "version": "1.0.0", 4 | "description": "A minimal Electron application", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "NODE_ENV=DEV concurrently \"nodemon server.js\" \"sleep 2 && electron .\"", 8 | "start-electron": "electron .", 9 | "start-server": "nodemon server.js", 10 | "bundle": "webpack", 11 | "watch": "NODE_ENV=DEV nodemon server.js", 12 | "start:prod": "webpack && electron .", 13 | "test": "karma start" 14 | }, 15 | "repository": "https://github.com/electron/electron-quick-start", 16 | "keywords": [ 17 | "Electron", 18 | "mern", 19 | "demo" 20 | ], 21 | "author": "Jennifer Nelson", 22 | "license": "MIT", 23 | "dependencies": { 24 | "axios": "^0.16.2", 25 | "body-parser": "^1.18.0", 26 | "express": "^4.15.4", 27 | "foreman": "^2.0.0", 28 | "install": "^0.10.1", 29 | "marked": "^0.3.6", 30 | "material-ui": "^0.19.1", 31 | "mongoose": "^4.11.11", 32 | "nodemon": "^1.12.0", 33 | "npm": "^5.4.1", 34 | "react-addons-css-transition-group": "^15.6.0", 35 | "react-dom": "^15.6.1", 36 | "react-material-icons": "^1.0.2", 37 | "react-redux": "^5.0.6", 38 | "react-router": "^4.2.0", 39 | "react-scripts": "1.0.13", 40 | "react-transition-group": "^2.2.0", 41 | "redux": "^3.7.2", 42 | "redux-form": "^7.0.4", 43 | "redux-promise": "^0.5.3" 44 | }, 45 | "devDependencies": { 46 | "babel-core": "^6.26.0", 47 | "babel-jest": "^21.0.2", 48 | "babel-loader": "^7.1.2", 49 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 50 | "babel-polyfill": "^6.26.0", 51 | "babel-preset-es2015": "^6.24.1", 52 | "babel-preset-react": "^6.24.1", 53 | "babel-preset-react-hmre": "^1.1.1", 54 | "concurrently": "^3.5.0", 55 | "css-loader": "^0.28.7", 56 | "d3": "^4.10.2", 57 | "electron": "~1.6.2", 58 | "expect": "^21.1.0", 59 | "jasmine": "^2.8.0", 60 | "jasmine-core": "^2.8.0", 61 | "jest": "^21.0.2", 62 | "karma": "^1.7.1", 63 | "karma-chrome-launcher": "^2.2.0", 64 | "karma-cli": "^1.0.1", 65 | "karma-jasmine": "^1.1.0", 66 | "karma-mocha": "^1.3.0", 67 | "karma-sourcemap-loader": "^0.3.7", 68 | "karma-webpack": "^2.0.4", 69 | "node-sass": "^4.5.3", 70 | "react": "^15.6.1", 71 | "react-test-renderer": "^15.6.1", 72 | "redux-logger": "^3.0.6", 73 | "sass-loader": "^6.0.6", 74 | "style-loader": "^0.18.2", 75 | "webpack": "^3.6.0", 76 | "webpack-dev-middleware-webpack-2": "^1.5.1", 77 | "webpack-hot-middleware": "^2.19.1" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /renderer.js: -------------------------------------------------------------------------------- 1 | // This file is required by the index.html file and will 2 | // be executed in the renderer process for that window. 3 | // All of the Node.js APIs are available in this process. 4 | import App from "./src/todos/components/App"; 5 | 6 | const React = require('react'); 7 | const ReactDOM = require('react-dom'); 8 | import { Provider } from 'react-redux'; 9 | import configureStore from './src/todos/store/configureStore.dev.js'; 10 | 11 | const store = configureStore(); 12 | 13 | ReactDOM.render( 14 | 15 | 16 | 17 | , document.getElementById('app')); -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // var path = require('path'); 2 | // var webpack = require('webpack'); 3 | // var express = require('express'); 4 | // var config = require('./webpack.config'); 5 | // 6 | // var app = express(); 7 | // var compiler = webpack(config); 8 | // 9 | // const port = process.env.PORT || 8080; 10 | // 11 | // app.use(require('webpack-dev-middleware')(compiler, { 12 | // noInfo: true, 13 | // publicPath: config.output.publicPath, 14 | // hot: true, 15 | // inline: true, 16 | // })); 17 | // 18 | // app.use(require('webpack-hot-middleware')(compiler)); 19 | // 20 | // app.get('*', function(req, res) { 21 | // res.sendFile(path.join(__dirname, 'index.html')); 22 | // }); 23 | // 24 | // app.listen(port, function(err) { 25 | // if (err) { 26 | // return console.error(err); 27 | // } 28 | // console.log(`Listening at http://localhost:${port}/`); 29 | // }) 30 | 31 | 'use strict' 32 | var path = require('path'); 33 | var webpack = require('webpack'); 34 | var express = require('express'); 35 | var config = require('./webpack.config'); 36 | // first we import our dependencies… 37 | var express = require('express'); 38 | var mongoose = require('mongoose'); 39 | var bodyParser = require('body-parser'); 40 | var Todo = require('./model/todo'); 41 | var app = express(); 42 | var compiler = webpack(config); 43 | var router = express.Router(); 44 | //set our port to either a predetermined port number if you have set 45 | //it up, or 3001 46 | // var port = process.env.API_PORT || 3001; 47 | const port = process.env.PORT || 8080; 48 | 49 | app.use(require('webpack-dev-middleware')(compiler, { 50 | noInfo: true, 51 | publicPath: config.output.publicPath, 52 | hot: true, 53 | inline: true, 54 | })); 55 | 56 | app.use(require('webpack-hot-middleware')(compiler)); 57 | 58 | 59 | mongoose.connect('localhost:27017/electron-mern-todo'); 60 | //now we should configure the API to use bodyParser and look for 61 | //JSON data in the request body 62 | app.use(bodyParser.urlencoded({ extended: true })); 63 | app.use(bodyParser.json()); 64 | //To prevent errors from Cross Origin Resource Sharing, we will set 65 | //our headers to allow CORS with middleware like so: 66 | app.use(function(req, res, next) { 67 | res.setHeader('Access-Control-Allow-Origin', '*'); 68 | res.setHeader('Access-Control-Allow-Credentials', 'true'); 69 | res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,OPTIONS,POST,PUT,DELETE'); 70 | res.setHeader('Access-Control-Allow-Headers', 'Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers'); 71 | //and remove cacheing so we get the most recent todos 72 | res.setHeader('Cache-Control', 'no-cache'); 73 | next(); 74 | }); 75 | //now we can set the route path & initialize the API 76 | router.get('/', function(req, res) { 77 | res.json({ message: 'API Initialized!'}); 78 | }); 79 | //adding the /todos route to our /api router 80 | router.route('/todos') 81 | //retrieve all todos from the database 82 | .get(function(req, res) { 83 | //looks at our Todo Schema 84 | Todo.find(function(err, todos) { 85 | if (err) 86 | res.send(err); 87 | //responds with a json object of our database todos. 88 | res.json(todos) 89 | }); 90 | }) 91 | //post new todo to the database 92 | .post(function(req, res) { 93 | var todo = new Todo(); 94 | //body parser lets us use the req.body 95 | todo.title = req.body.title; 96 | todo.completed = req.body.completed ? req.body.completed : false; 97 | todo.save(function(err) { 98 | if (err) 99 | res.send(err); 100 | res.json(todo); 101 | }); 102 | }); 103 | //Adding a route to a specific todos based on the database ID 104 | router.route('/todos/:todo_id') 105 | //The put method gives us the chance to update our todo based on 106 | //the ID passed to the route 107 | .put(function(req, res) { 108 | Todo.findById(req.params.todo_id, function(err, todo) { 109 | if (err) 110 | res.send(err); 111 | //setting the new title, completed to whatever was changed. If 112 | //nothing was changed we will not alter the field. 113 | (req.body.title) ? todo.title = req.body.title : null; 114 | (req.body.completed !== undefined) ? todo.completed = req.body.completed : false; 115 | //save todo 116 | todo.save(function(err) { 117 | if (err) 118 | res.send(err); 119 | res.json({ message: 'ToDo has been updated' }); 120 | }); 121 | }); 122 | }) 123 | //delete method for removing a todo from our database 124 | .delete(function(req, res) { 125 | //selects the todo by its ID, then removes it. 126 | Todo.remove({ _id: req.params.todo_id }, function(err, todo) { 127 | if (err) 128 | res.send(err); 129 | res.json({ message: 'To Do has been deleted' }) 130 | }) 131 | }); 132 | //Use our router configuration when we call /api 133 | app.use('/api', router); 134 | //starts the server and listens for requests 135 | 136 | app.get('*', function(req, res) { 137 | res.sendFile(path.join(__dirname, 'index.html')); 138 | }); 139 | 140 | app.listen(port, function(err) { 141 | if (err) { 142 | return console.error(err); 143 | } 144 | console.log(`Listening at http://localhost:${port}/`); 145 | }) 146 | 147 | //server.js 148 | // 'use strict' 149 | //first we import our dependencies… 150 | // var express = require('express'); 151 | // var mongoose = require('mongoose'); 152 | // var bodyParser = require('body-parser'); 153 | // var Comment = require('./model/comments'); 154 | // //and create our instances 155 | // var app = express(); 156 | // var router = express.Router(); 157 | // //set our port to either a predetermined port number if you have set 158 | // //it up, or 3001 159 | // var port = process.env.API_PORT || 3001; 160 | // mongoose.connect('localhost:27017/electron-mern-todo'); 161 | // //now we should configure the API to use bodyParser and look for 162 | // //JSON data in the request body 163 | // app.use(bodyParser.urlencoded({ extended: true })); 164 | // app.use(bodyParser.json()); 165 | // //To prevent errors from Cross Origin Resource Sharing, we will set 166 | // //our headers to allow CORS with middleware like so: 167 | // app.use(function(req, res, next) { 168 | // res.setHeader('Access-Control-Allow-Origin', '*'); 169 | // res.setHeader('Access-Control-Allow-Credentials', 'true'); 170 | // res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,OPTIONS,POST,PUT,DELETE'); 171 | // res.setHeader('Access-Control-Allow-Headers', 'Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers'); 172 | // //and remove cacheing so we get the most recent comments 173 | // res.setHeader('Cache-Control', 'no-cache'); 174 | // next(); 175 | // }); 176 | // //now we can set the route path & initialize the API 177 | // router.get('/', function(req, res) { 178 | // res.json({ message: 'API Initialized!'}); 179 | // }); 180 | // //adding the /comments route to our /api router 181 | // router.route('/comments') 182 | // //retrieve all comments from the database 183 | // .get(function(req, res) { 184 | // //looks at our Comment Schema 185 | // Comment.find(function(err, comments) { 186 | // if (err) 187 | // res.send(err); 188 | // //responds with a json object of our database comments. 189 | // res.json(comments) 190 | // }); 191 | // }) 192 | // //post new comment to the database 193 | // .post(function(req, res) { 194 | // var comment = new Comment(); 195 | // //body parser lets us use the req.body 196 | // comment.author = req.body.author; 197 | // comment.text = req.body.text; 198 | // comment.save(function(err) { 199 | // if (err) 200 | // res.send(err); 201 | // res.json({ message: 'Comment successfully added!' }); 202 | // }); 203 | // }); 204 | // //Adding a route to a specific comment based on the database ID 205 | // router.route('/comments/:comment_id') 206 | // //The put method gives us the chance to update our comment based on 207 | // //the ID passed to the route 208 | // .put(function(req, res) { 209 | // Comment.findById(req.params.comment_id, function(err, comment) { 210 | // if (err) 211 | // res.send(err); 212 | // //setting the new author and text to whatever was changed. If 213 | // //nothing was changed we will not alter the field. 214 | // (req.body.author) ? comment.author = req.body.author : null; 215 | // (req.body.text) ? comment.text = req.body.text : null; 216 | // //save comment 217 | // comment.save(function(err) { 218 | // if (err) 219 | // res.send(err); 220 | // res.json({ message: 'Comment has been updated' }); 221 | // }); 222 | // }); 223 | // }) 224 | // //delete method for removing a comment from our database 225 | // .delete(function(req, res) { 226 | // //selects the comment by its ID, then removes it. 227 | // Comment.remove({ _id: req.params.comment_id }, function(err, comment) { 228 | // if (err) 229 | // res.send(err); 230 | // res.json({ message: 'Comment has been deleted' }) 231 | // }) 232 | // }); 233 | // //Use our router configuration when we call /api 234 | // app.use('/api', router); 235 | // //starts the server and listens for requests 236 | // app.listen(port, function() { 237 | // console.log('api running on port: ' + port); 238 | // }); -------------------------------------------------------------------------------- /src/electron-starter.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron'); 2 | // Module to control application life. 3 | const app = electron.app; 4 | // Module to create native browser window. 5 | const BrowserWindow = electron.BrowserWindow; 6 | 7 | const path = require('path'); 8 | const url = require('url'); 9 | 10 | // Keep a global reference of the window object, if you don't, the window will 11 | // be closed automatically when the JavaScript object is garbage collected. 12 | let mainWindow; 13 | 14 | function createWindow() { 15 | // Create the browser window. 16 | mainWindow = new BrowserWindow({width: 800, height: 600}); 17 | 18 | // and load the index.html of the app. 19 | mainWindow.loadURL('http://localhost:3000'); 20 | 21 | // Open the DevTools. 22 | mainWindow.webContents.openDevTools(); 23 | 24 | // Emitted when the window is closed. 25 | mainWindow.on('closed', function () { 26 | // Dereference the window object, usually you would store windows 27 | // in an array if your app supports multi windows, this is the time 28 | // when you should delete the corresponding element. 29 | mainWindow = null 30 | }) 31 | } 32 | 33 | // This method will be called when Electron has finished 34 | // initialization and is ready to create browser windows. 35 | // Some APIs can only be used after this event occurs. 36 | app.on('ready', createWindow); 37 | 38 | // Quit when all windows are closed. 39 | app.on('window-all-closed', function () { 40 | // On OS X it is common for applications and their menu bar 41 | // to stay active until the user quits explicitly with Cmd + Q 42 | if (process.platform !== 'darwin') { 43 | app.quit() 44 | } 45 | }); 46 | 47 | app.on('activate', function () { 48 | // On OS X it's common to re-create a window in the app when the 49 | // dock icon is clicked and there are no other windows open. 50 | if (mainWindow === null) { 51 | createWindow() 52 | } 53 | }); 54 | 55 | // In this file you can include the rest of your app's specific main process 56 | // code. You can also put them in separate files and require them here. -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (!isLocalhost) { 36 | // Is not local host. Just register service worker 37 | registerValidSW(swUrl); 38 | } else { 39 | // This is running on localhost. Lets check if a service worker still exists or not. 40 | checkValidServiceWorker(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/style.js: -------------------------------------------------------------------------------- 1 | //style.js 2 | const style = { 3 | body: { 4 | fontFamily: 'Roboto' 5 | }, 6 | todoBoard: { 7 | margin: '50px', 8 | paddingTop: '20px' 9 | }, 10 | todoTable: { 11 | width:'80vw', 12 | margin:'5px 0px 5px 20px', 13 | fontFamily:'Helvetica, sans-serif' 14 | }, 15 | title: { 16 | marginLeft: '50px', 17 | textAlign:'left', 18 | textTransform:'uppercase' 19 | }, 20 | todoList: { 21 | border:'1px solid #f1f1f1', 22 | padding:'0 12px', 23 | maxHeight:'70vh', 24 | overflow:'scroll' 25 | }, 26 | todo: { 27 | backgroundColor:'#fafafa', 28 | margin:'10px', 29 | padding:'3px 10px', 30 | fontSize:'.85rem' 31 | }, 32 | todoForm: { 33 | marginTop: '50px', 34 | marginLeft:'50px', 35 | paddingBottom: '50px', 36 | justifyContent:'space-between' 37 | }, 38 | TodoContainer: { 39 | marginTop: '50px', 40 | marginLeft:'50px', 41 | paddingBottom: '50px', 42 | justifyContent:'space-between' 43 | }, 44 | todoFormTitle: { 45 | minWidth:'150px', 46 | margin:'3px', 47 | padding:'0 10px', 48 | borderRadius:'3px', 49 | height:'40px', 50 | flex:'2' 51 | }, 52 | todoFormTextField: { 53 | marginLeft:' 25px', 54 | minWidth:'380px', 55 | fontFamily: 'Indie Flower', 56 | fontSize: '1.5em' 57 | }, 58 | todoFormButton: { 59 | marginLeft:'15px' 60 | }, 61 | todoFormCompleted: { 62 | margin:'3px', 63 | padding:'0 10px', 64 | height:'40px', 65 | flex:'2' 66 | }, 67 | todoFormPost: { 68 | minWidth:'75px', 69 | flex:'1', 70 | height:'40px', 71 | margin:'5px 3px', 72 | fontSize:'1rem', 73 | backgroundColor:'#A3CDFD', 74 | borderRadius:'3px', 75 | color:'#fff', 76 | textTransform:'uppercase', 77 | letterSpacing:'.055rem', 78 | border:'none' 79 | }, 80 | deleteLink: { 81 | color: '#777', 82 | fontSize:'8px' 83 | }, 84 | updateLink: { 85 | color: '#777', 86 | fontSize:'8px' 87 | }, 88 | toDoStyle: { 89 | fontFamily: 'Indie Flower', 90 | fontSize: '2em', 91 | color: '#00BCD4', 92 | marginLeft: '10px', 93 | marginTop: '-10px', 94 | textDecoration: 'none' 95 | }, 96 | completedToDoStyle: { 97 | fontFamily: 'Indie Flower', 98 | fontSize: '2em', 99 | color: '#00BCD4', 100 | marginLeft: '10px', 101 | marginTop: '-10px', 102 | textDecoration: 'line-through' 103 | }, 104 | completedStyle: { 105 | color: '#777', 106 | textAlign: 'left' 107 | }, 108 | exampleEnter: { 109 | opacity: "0.01" 110 | }, 111 | exampleEnterActive: { 112 | opacity: '1', 113 | transition: 'opacity 500ms ease-in' 114 | }, 115 | exampleLeave: { 116 | opacity: '1' 117 | }, 118 | exampleLeaveActive: { 119 | opacity: '0.01', 120 | transition: 'opacity 300ms ease-in' 121 | } 122 | } 123 | module.exports = style; -------------------------------------------------------------------------------- /src/todos/actions/index-test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import * as actions from './index'; 3 | 4 | describe('Actions', () => { 5 | it('should create a FETCH_TODOS_FAILURE action', () => { 6 | const err = { 7 | message: 'bad request' 8 | } 9 | const expectedAction = { 10 | type: 'FETCH_TODOS_FAILURE', 11 | }; 12 | expect(actions.fetchTodosFailure(err).type).toEqual(expectedAction.type); 13 | }); 14 | }); -------------------------------------------------------------------------------- /src/todos/actions/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | //To Do list 4 | export const FETCH_TODOS = 'FETCH_TODOS'; 5 | export const FETCH_TODOS_SUCCESS = 'FETCH_TODOS_SUCCESS'; 6 | export const FETCH_TODOS_FAILURE = 'FETCH_TODOS_FAILURE'; 7 | export const RESET_TODOS = 'RESET_TODOS'; 8 | 9 | //Create new to do 10 | export const CREATE_TODO = 'CREATE_TODO'; 11 | export const CREATE_TODO_SUCCESS = 'CREATE_TODO_SUCCESS'; 12 | export const CREATE_TODO_FAILURE = 'CREATE_TODO_FAILURE'; 13 | export const RESET_NEW_TODO = 'RESET_NEW_TODO'; 14 | 15 | //Update to do 16 | export const UPDATE_TODO = 'UPDATE_TODO'; 17 | export const UPDATE_TODO_SUCCESS = 'CREATE_TODO_SUCCESS'; 18 | export const UPDATE_TODO_FAILURE = 'CREATE_TODO_FAILURE'; 19 | 20 | //Fetch to do 21 | export const FETCH_TODO = 'FETCH_TODO'; 22 | export const FETCH_TODO_SUCCESS = 'FETCH_TODO_SUCCESS'; 23 | export const FETCH_TODO_FAILURE = 'FETCH_TODO_FAILURE'; 24 | export const RESET_ACTIVE_TODO = 'RESET_ACTIVE_TODO'; 25 | 26 | //Delete to do 27 | export const DELETE_TODO = 'DELETE_TODO'; 28 | export const DELETE_TODO_SUCCESS = 'DELETE_TODO_SUCCESS'; 29 | export const DELETE_TODO_FAILURE = 'DELETE_TODO_FAILURE'; 30 | export const RESET_DELETED_TODO = 'RESET_DELETED_TODO'; 31 | 32 | const ROOT_URL = 'http://localhost:8080/api'; 33 | export function fetchTodos() { 34 | const request = axios.get('http://localhost:8080/api/todos/'); 35 | return { 36 | type: FETCH_TODOS, 37 | payload: request 38 | }; 39 | } 40 | 41 | export function fetchTodosSuccess(todos) { 42 | return { 43 | type: FETCH_TODOS_SUCCESS, 44 | payload: todos 45 | }; 46 | } 47 | 48 | export function fetchTodosFailure(error) { 49 | return { 50 | type: FETCH_TODOS_FAILURE, 51 | payload: error 52 | }; 53 | } 54 | 55 | export function resetTodoFields() { 56 | return { 57 | type: RESET_TODO_FIELDS 58 | }; 59 | } 60 | 61 | export function createTodo(newTodo) { 62 | const request = axios.post('http://localhost:8080/api/todos', newTodo); 63 | 64 | return { 65 | type: CREATE_TODO, 66 | payload: request 67 | }; 68 | } 69 | 70 | export function createTodoSuccess(newTodo) { 71 | console.log('create todo success: ', newTodo); 72 | return { 73 | type: CREATE_TODO_SUCCESS, 74 | payload: newTodo 75 | }; 76 | } 77 | 78 | export function createTodoFailure(error) { 79 | return { 80 | type: CREATE_TODO_FAILURE, 81 | payload: error 82 | }; 83 | } 84 | 85 | export function resetNewTodo() { 86 | return { 87 | type: RESET_NEW_TODO 88 | } 89 | } 90 | 91 | export function updateTodo(id, todo) { 92 | const request = axios.put('http://localhost:8080/api/todos/' + id, todo); 93 | return { 94 | type: UPDATE_TODO, 95 | payload: request 96 | }; 97 | } 98 | 99 | export function updateTodoSuccess(todo) { 100 | console.log('update todo success: ', todo); 101 | return { 102 | type: UPDATE_TODO_SUCCESS, 103 | payload: todo 104 | }; 105 | } 106 | 107 | export function updateTodoFailure(error) { 108 | return { 109 | type: UPDATE_TODO_FAILURE, 110 | payload: error 111 | }; 112 | } 113 | 114 | export function resetDeletedTodo() { 115 | return { 116 | type: RESET_DELETED_TODO 117 | }; 118 | } 119 | 120 | 121 | export function fetchTodo(id) { 122 | const request = axios.get('http://localhost:8080/api/todos/' + id); 123 | 124 | return { 125 | type: FETCH_TODO, 126 | payload: request 127 | }; 128 | } 129 | 130 | 131 | export function fetchTodoSuccess(activeTodo) { 132 | return { 133 | type: FETCH_TODO_SUCCESS, 134 | payload: activeTodo 135 | }; 136 | } 137 | 138 | export function fetchTodoFailure(error) { 139 | return { 140 | type: FETCH_TODO_FAILURE, 141 | payload: error 142 | }; 143 | } 144 | 145 | export function resetActiveTodo() { 146 | return { 147 | type: RESET_ACTIVE_TODO 148 | } 149 | } 150 | 151 | 152 | export function deleteTodo(id) { 153 | const request = axios.delete('http://localhost:8080/api/todos/' + id) 154 | 155 | return { 156 | type: DELETE_TODO, 157 | payload: request 158 | }; 159 | } 160 | 161 | export function deleteTodoSuccess(deletedTodo) { 162 | return { 163 | type: DELETE_TODO_SUCCESS, 164 | payload: deletedTodo 165 | }; 166 | } 167 | 168 | export function deleteTodoFailure(response) { 169 | return { 170 | type: DELETE_TODO_FAILURE, 171 | payload: response 172 | }; 173 | } -------------------------------------------------------------------------------- /src/todos/components/App.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react'; 3 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 4 | import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme'; 5 | import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme'; 6 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 7 | import TodoBoard from './TodoBoard'; 8 | 9 | class App extends Component { 10 | render() { 11 | return ( 12 | 13 | 16 | 17 | ); 18 | } 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /src/todos/components/Todo.js: -------------------------------------------------------------------------------- 1 | //Todo.js 2 | import React, { Component } from 'react'; 3 | import style from '../../style'; 4 | import CheckBox from 'react-material-icons/icons/toggle/check-box'; 5 | import CheckBoxOutline from 'react-material-icons/icons/toggle/check-box-outline-blank'; 6 | import Delete from 'react-material-icons/icons/action/delete'; 7 | import IconButton from 'material-ui/IconButton'; 8 | import { 9 | TableRow, 10 | TableRowColumn, 11 | } from 'material-ui/Table'; 12 | 13 | 14 | class Todo extends Component { 15 | constructor(props) { 16 | super(props); 17 | this.state= { 18 | toBeUpdated: false, 19 | title: props.title, 20 | completed: props.completed 21 | }; 22 | //binding all our functions to this class 23 | this.deleteTodo = this.deleteTodo.bind(this); 24 | this.toggleComplete = this.toggleComplete.bind(this); 25 | } 26 | toggleComplete(e) { 27 | e.preventDefault(); 28 | let id = this.props.uniqueID; 29 | let title = (this.state.title) ? this.state.title : null; 30 | let completed = !this.state.completed ; 31 | this.setState({ completed: !this.state.completed }); 32 | let todo = { title: title, completed: completed}; 33 | this.props.updateTodo(id, todo); 34 | } 35 | deleteTodo(e) { 36 | e.preventDefault(); 37 | let id = this.props.uniqueID; 38 | this.props.deleteTodo(id); 39 | } 40 | 41 | render() { 42 | let checkbox = null; 43 | if (this.state.completed) { 44 | checkbox = 45 | } else { 46 | checkbox = 47 | } 48 | return ( 49 | 50 | 51 | 52 | 53 | 54 | 55 | { this.props.title } 56 | 57 | 58 | 59 | {checkbox} 60 | 61 | 62 | 63 | ) 64 | } 65 | } 66 | 67 | export default Todo; -------------------------------------------------------------------------------- /src/todos/components/TodoBoard.js: -------------------------------------------------------------------------------- 1 | //TodoBoard.js 2 | import React, { Component } from 'react'; 3 | import TodoListContainer from '../containers/TodoListContainer'; 4 | import TodoFormContainer from '../containers/TodoFormContainer'; 5 | import style from '../../style'; 6 | import Paper from 'material-ui/Paper'; 7 | 8 | 9 | class TodoBoard extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { data: [] }; 13 | } 14 | render() { 15 | return ( 16 | 17 |
18 |

To Do:

19 | 22 | 23 |
24 |
25 | ) 26 | } 27 | } 28 | export default TodoBoard; -------------------------------------------------------------------------------- /src/todos/components/TodoList-test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amelieoller/electron-reactjs-mongo-express/e09fc9f3fc455a6d1826656e8e4a8335850b69dd/src/todos/components/TodoList-test.js -------------------------------------------------------------------------------- /src/todos/components/TodoList.js: -------------------------------------------------------------------------------- 1 | //TodoList.js 2 | import React, { Component } from 'react'; 3 | import Todo from './Todo'; 4 | import style from '../../style'; 5 | import { 6 | Table, 7 | TableBody, 8 | TableHeader, 9 | TableRow, 10 | TableHeaderColumn 11 | } from 'material-ui/Table'; 12 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; 13 | 14 | import TodoContainer from '../containers/TodoContainer'; 15 | 16 | 17 | class TodoList extends Component { 18 | componentWillMount() { 19 | this.props.fetchTodos(); 20 | } 21 | onCellHover(rowNumber, columnId) { 22 | console.log('on cell hover: ', rowNumber, columnId); 23 | } 24 | onCellHoverExit() { 25 | 26 | } 27 | renderTodos(todos) { 28 | let todoNodes = todos.map(todo => { 29 | return ( 30 | 37 | 38 | ) 39 | }) 40 | return ( 41 | 46 | 47 | { todoNodes } 48 | 49 |
50 | ) 51 | } 52 | render() { 53 | const { todos, loading, error } = this.props.todoList; 54 | 55 | if(loading) { 56 | return

Loading To Do list...

57 | } else if(error) { 58 | return
Error: {error.message}
59 | } 60 | 61 | return ( 62 | this.renderTodos(todos) 63 | ); 64 | } 65 | } 66 | export default TodoList; -------------------------------------------------------------------------------- /src/todos/containers/TodoContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { deleteTodo, deleteTodoFailure, updateTodo, updateTodoFailure, fetchTodos, fetchTodosSuccess, fetchTodosFailure } from '../actions/index'; 3 | import Todo from "../components/Todo"; 4 | 5 | 6 | const mapStateToProps = (state) => { 7 | return { 8 | todoList: state.todos.todoList 9 | }; 10 | } 11 | 12 | const mapDispatchToProps = (dispatch) => { 13 | return { 14 | fetchTodos: () => { 15 | dispatch(fetchTodos()).then((response) => { 16 | if (!response.error) { 17 | dispatch(fetchTodosSuccess(response.payload.data)) 18 | } else { 19 | dispatch(fetchTodosFailure(response.payload.data)); 20 | } 21 | }); 22 | }, 23 | deleteTodo: (id) => { 24 | console.log('deleting id: ', id); 25 | dispatch(deleteTodo(id)).then((response) => { 26 | if (!response.error) { 27 | console.log('Delete successful'); 28 | dispatch(fetchTodos()).then((response) => { 29 | dispatch(fetchTodosSuccess(response.payload.data)) 30 | }) 31 | } else { 32 | console.log('Delete failure'); 33 | dispatch(deleteTodoFailure(response.payload.data)) 34 | } 35 | }); 36 | }, 37 | updateTodo: (id, todo) => { 38 | dispatch(updateTodo(id, todo)).then((response) => { 39 | if (!response.error) { 40 | console.log('Update successful'); 41 | dispatch(fetchTodos()).then((response) => { 42 | dispatch(fetchTodosSuccess(response.payload.data)) 43 | }); 44 | } else { 45 | console.log('Update failure'); 46 | dispatch(updateTodoFailure(response.payload.data)) 47 | } 48 | }); 49 | } 50 | } 51 | } 52 | 53 | const TodoContainer = connect( 54 | mapStateToProps, 55 | mapDispatchToProps 56 | )(Todo) 57 | 58 | export default TodoContainer; -------------------------------------------------------------------------------- /src/todos/containers/TodoFormContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { createTodo, createTodoSuccess, fetchTodos, fetchTodosSuccess, createTodoFailure, resetNewTodo} from '../actions/index'; 4 | import style from '../../style'; 5 | import RaisedButton from 'material-ui/RaisedButton'; 6 | import TextField from 'material-ui/TextField'; 7 | 8 | let TodoFormContainer = ({ dispatch }) => { 9 | let input 10 | 11 | return ( 12 |
13 |
{ 16 | e.preventDefault() 17 | if (!input.input.value.trim()) { 18 | return 19 | } 20 | dispatch(createTodo({ title: input.input.value})).then((response) => { 21 | console.log('dispatching createToDoSuccess'); 22 | if (!response.error) { 23 | dispatch(fetchTodos()).then((response) => { 24 | dispatch(fetchTodosSuccess(response.payload.data)) 25 | }) 26 | } else { 27 | dispatch(createTodoFailure(response.payload.data)) 28 | } 29 | }); 30 | input.input.value = '' 31 | }} 32 | > 33 | { 38 | input = node 39 | }} 40 | /> 41 | 46 | 47 |
48 | ) 49 | } 50 | 51 | TodoFormContainer = connect()(TodoFormContainer) 52 | 53 | export default TodoFormContainer 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/todos/containers/TodoListContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { fetchTodos, fetchTodosSuccess, fetchTodosFailure } from '../actions/index'; 3 | import TodoList from '../components/TodoList'; 4 | 5 | 6 | const mapStateToProps = (state) => { 7 | return { 8 | todoList: state.todos.todoList 9 | }; 10 | } 11 | 12 | const mapDispatchToProps = (dispatch) => { 13 | return { 14 | fetchTodos: () => { 15 | dispatch(fetchTodos()).then((response) => { 16 | !response.error ? dispatch(fetchTodosSuccess(response.payload.data)) : dispatch(fetchTodosFailure(response.payload.data)); 17 | }); 18 | } 19 | } 20 | } 21 | 22 | const TodoListContainer = connect( 23 | mapStateToProps, 24 | mapDispatchToProps 25 | )(TodoList) 26 | 27 | export default TodoListContainer; -------------------------------------------------------------------------------- /src/todos/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import todos from './todos' 3 | 4 | const todoApp = combineReducers({ 5 | todos 6 | }) 7 | 8 | export default todoApp -------------------------------------------------------------------------------- /src/todos/reducers/todos-test.js: -------------------------------------------------------------------------------- 1 | import {todos} from './todos'; 2 | import * as ActionType from '../actions/index'; 3 | 4 | -------------------------------------------------------------------------------- /src/todos/reducers/todos.js: -------------------------------------------------------------------------------- 1 | import { 2 | FETCH_TODOS, FETCH_TODOS_SUCCESS, FETCH_TODOS_FAILURE, RESET_TODOS, 3 | FETCH_TODO, FETCH_TODO_SUCCESS, FETCH_TODO_FAILURE, RESET_ACTIVE_TODO, 4 | CREATE_TODO, CREATE_TODO_SUCCESS, CREATE_TODO_FAILURE, RESET_NEW_TODO, 5 | DELETE_TODO, DELETE_TODO_SUCCESS, DELETE_TODO_FAILURE, RESET_DELETED_TODO, 6 | RESET_TODO_FIELDS 7 | } from '../actions/index'; 8 | 9 | 10 | const INITIAL_STATE = { 11 | todoList: {todos: [], error: null, loading: false}, 12 | newTodo:{todo: null, error: null, loading: false}, 13 | activeTodo:{todo: null, error: null, loading: false}, 14 | deletedTodo: {todo: null, error: null, loading: false} 15 | }; 16 | 17 | const todos = (state = INITIAL_STATE, action) => { 18 | let error; 19 | switch(action.type) { 20 | 21 | case FETCH_TODOS:// start fetching and set loading = true 22 | return { ...state, todoList: {todos:[], error: null, loading: true} }; 23 | case FETCH_TODOS_SUCCESS:// return list of todos and make loading = false 24 | return { ...state, todoList: {todos: action.payload, error: null, loading: false} }; 25 | case FETCH_TODOS_FAILURE:// return error and make loading = false 26 | error = action.payload || {message: action.payload.message};//2nd one is network or server down errors 27 | return { ...state, todoList: {todos: [], error: error, loading: false} }; 28 | case RESET_TODOS:// reset todoList to initial state 29 | return { ...state, todoList: {todos: [], error: null, loading: false} }; 30 | 31 | case FETCH_TODO: 32 | return { ...state, activeTodo:{...state.activeTodo, loading: true}}; 33 | case FETCH_TODO_SUCCESS: 34 | return { ...state, activeTodo: {todo: action.payload, error: null, loading: false}}; 35 | case FETCH_TODO_FAILURE: 36 | error = action.payload || {message: action.payload.message};//2nd one is network or server down errors 37 | return { ...state, activeTodo: {todo: null, error: error, loading:false}}; 38 | case RESET_ACTIVE_TODO: 39 | return { ...state, activeTodo: {todo: null, error: null, loading: false}}; 40 | 41 | case CREATE_TODO: 42 | return {...state, newTodo: {...state.newTodo, loading: true}} 43 | case CREATE_TODO_SUCCESS: 44 | return {...state, newTodo: {todo: action.payload, error: null, loading: false}} 45 | case CREATE_TODO_FAILURE: 46 | error = action.payload || {message: action.payload.message};//2nd one is network or server down errors 47 | return {...state, newTodo: {todo: null, error: error, loading: false}} 48 | case RESET_NEW_TODO: 49 | return {...state, newTodo:{todo: null, error: null, loading: false}} 50 | 51 | case DELETE_TODO: 52 | return {...state, deletedTodo: {...state.deletedTodo, loading: true}} 53 | case DELETE_TODO_SUCCESS: 54 | return {...state, deletedTodo: {todo: action.payload, error: null, loading: false}} 55 | case DELETE_TODO_FAILURE: 56 | error = action.payload || {message: action.payload.message};//2nd one is network or server down errors 57 | return {...state, deletedTodo: {todo: null, error: error, loading: false}} 58 | case RESET_DELETED_TODO: 59 | return {...state, deletedTodo:{todo: null, error: null, loading: false}} 60 | 61 | case RESET_TODO_FIELDS: 62 | return {...state, newTodo:{...state.newTodo, error: null, loading: null}} 63 | default: 64 | return state; 65 | } 66 | } 67 | 68 | export default todos -------------------------------------------------------------------------------- /src/todos/store/configureStore.dev.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import promise from 'redux-promise'; 3 | import todoApp from '../reducers'; 4 | import { createLogger } from 'redux-logger'; 5 | 6 | const logger = createLogger(); 7 | 8 | export default function configureStore(initialState) { 9 | 10 | if (module.hot) { 11 | // Enable Webpack hot module replacement for reducers 12 | module.hot.accept('../reducers', () => { 13 | const nextReducer = require('../reducers'); 14 | store.replaceReducer(nextReducer); 15 | }); 16 | } 17 | 18 | return createStore( 19 | todoApp, 20 | initialState, 21 | applyMiddleware( 22 | promise, 23 | logger 24 | ) 25 | ) 26 | }; -------------------------------------------------------------------------------- /src/todos/store/configureStore.js: -------------------------------------------------------------------------------- 1 | // import configureStore from './store/configureStore.dev'; 2 | // 3 | // if (process.env.NODE_ENV === 'production' || (location && location.hostname !== 'localhost')) { 4 | // module.exports = require('./configureStore.prod'); 5 | // } else { 6 | // const store 7 | // } -------------------------------------------------------------------------------- /src/todos/store/configureStore.prod.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import rootReducer from '../reducers'; 3 | import promise from 'redux-promise'; 4 | 5 | // Middleware you want to use in production: 6 | const enhancer = applyMiddleware(promise); 7 | 8 | export default function configureStore(initialState) { 9 | // Note: only Redux >= 3.1.0 supports passing enhancer as third argument. 10 | // See https://github.com/rackt/redux/releases/tag/v3.1.0 11 | return createStore(rootReducer, initialState, enhancer); 12 | }; -------------------------------------------------------------------------------- /tests.webpack.js: -------------------------------------------------------------------------------- 1 | // tests.webpack.js 2 | var context = require.context('./src', true, /-test\.jsx?$/); 3 | context.keys().forEach(context); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | const port = process.env.PORT || '8080'; 5 | 6 | const config = { 7 | context: __dirname, 8 | entry: [ 9 | 'babel-polyfill', 10 | path.resolve(__dirname, './renderer.js'), 11 | `webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`, 12 | ], 13 | target: 'electron-renderer', 14 | output: { 15 | filename: 'renderer.bundle.js', 16 | path: __dirname + '/bundle', 17 | publicPath: `http://localhost:${port}/bundle/`, 18 | libraryTarget: 'commonjs2' 19 | }, 20 | module: { 21 | loaders: [ 22 | { test: /\.js$/, 23 | loader: 'babel-loader', 24 | include: [ 25 | path.join(__dirname, 'src'), 26 | path.join(__dirname, 'renderer.js') 27 | ], 28 | exclude: /node_modules/, 29 | query: { 30 | presets: ['es2015', 'react', 'react-hmre'] 31 | } 32 | }, 33 | { test: /\.scss$/, loader: 'style-loader!css-loader!sass-loader' }, 34 | { 35 | test: /\.json$/, 36 | loader: 'json-loader' 37 | } 38 | ] 39 | }, 40 | plugins: [ 41 | new webpack.HotModuleReplacementPlugin(), 42 | ] 43 | }; 44 | 45 | module.exports = config; --------------------------------------------------------------------------------